diff options
Diffstat (limited to 'sys/xvboimage/xvboimagesink.c')
-rw-r--r-- | sys/xvboimage/xvboimagesink.c | 3917 |
1 files changed, 3917 insertions, 0 deletions
diff --git a/sys/xvboimage/xvboimagesink.c b/sys/xvboimage/xvboimagesink.c new file mode 100644 index 00000000..c14a1d22 --- /dev/null +++ b/sys/xvboimage/xvboimagesink.c @@ -0,0 +1,3917 @@ +/* GStreamer + * Copyright (C) <2005> Julien Moutte <julien@moutte.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-xvimagesink + * + * XvboImageSink renders video frames to a drawable (XWindow) on a local display + * using the XVideo extension. Rendering to a remote display is theorically + * possible but i doubt that the XVideo extension is actually available when + * connecting to a remote display. This element can receive a Window ID from the + * application through the XOverlay interface and will then render video frames + * in this drawable. If no Window ID was provided by the application, the + * element will create its own internal window and render into it. + * + * <refsect2> + * <title>Scaling</title> + * <para> + * The XVideo extension, when it's available, handles hardware accelerated + * scaling of video frames. This means that the element will just accept + * incoming video frames no matter their geometry and will then put them to the + * drawable scaling them on the fly. Using the #GstXvBoImageSink:force-aspect-ratio + * property it is possible to enforce scaling with a constant aspect ratio, + * which means drawing black borders around the video frame. + * </para> + * </refsect2> + * <refsect2> + * <title>Events</title> + * <para> + * XvboImageSink creates a thread to handle events coming from the drawable. There + * are several kind of events that can be grouped in 2 big categories: input + * events and window state related events. Input events will be translated to + * navigation events and pushed upstream for other elements to react on them. + * This includes events such as pointer moves, key press/release, clicks etc... + * Other events are used to handle the drawable appearance even when the data + * is not flowing (GST_STATE_PAUSED). That means that even when the element is + * paused, it will receive expose events from the drawable and draw the latest + * frame with correct borders/aspect-ratio. + * </para> + * </refsect2> + * <refsect2> + * <title>Pixel aspect ratio</title> + * <para> + * When changing state to GST_STATE_READY, XvboImageSink will open a connection to + * the display specified in the #GstXvBoImageSink:display property or the + * default display if nothing specified. Once this connection is open it will + * inspect the display configuration including the physical display geometry and + * then calculate the pixel aspect ratio. When receiving video frames with a + * different pixel aspect ratio, XvboImageSink will use hardware scaling to + * display the video frames correctly on display's pixel aspect ratio. + * Sometimes the calculated pixel aspect ratio can be wrong, it is + * then possible to enforce a specific pixel aspect ratio using the + * #GstXvBoImageSink:pixel-aspect-ratio property. + * </para> + * </refsect2> + * <refsect2> + * <title>Examples</title> + * |[ + * gst-launch -v videotestsrc ! xvboimagesink + * ]| A pipeline to test hardware scaling. + * When the test video signal appears you can resize the window and see that + * video frames are scaled through hardware (no extra CPU cost). + * |[ + * gst-launch -v videotestsrc ! xvboimagesink force-aspect-ratio=true + * ]| Same pipeline with #GstXvBoImageSink:force-aspect-ratio property set to true + * You can observe the borders drawn around the scaled image respecting aspect + * ratio. + * |[ + * gst-launch -v videotestsrc ! navigationtest ! xvboimagesink + * ]| A pipeline to test navigation events. + * While moving the mouse pointer over the test signal you will see a black box + * following the mouse pointer. If you press the mouse button somewhere on the + * video and release it somewhere else a green box will appear where you pressed + * the button and a red one where you released it. (The navigationtest element + * is part of gst-plugins-good.) You can observe here that even if the images + * are scaled through hardware the pointer coordinates are converted back to the + * original video frame geometry so that the box can be drawn to the correct + * position. This also handles borders correctly, limiting coordinates to the + * image area + * |[ + * gst-launch -v videotestsrc ! video/x-raw-yuv, pixel-aspect-ratio=(fraction)4/3 ! xvboimagesink + * ]| This is faking a 4/3 pixel aspect ratio caps on video frames produced by + * videotestsrc, in most cases the pixel aspect ratio of the display will be + * 1/1. This means that XvboImageSink will have to do the scaling to convert + * incoming frames to a size that will match the display pixel aspect ratio + * (from 320x240 to 320x180 in this case). Note that you might have to escape + * some characters for your shell like '\(fraction\)'. + * |[ + * gst-launch -v videotestsrc ! xvboimagesink hue=100 saturation=-100 brightness=100 + * ]| Demonstrates how to use the colorbalance interface. + * </refsect2> + */ + +/* for developers: there are two useful tools : xvinfo and xvattr */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Our interfaces */ +#include <gst/interfaces/navigation.h> +#include <gst/interfaces/xoverlay.h> +#include <gst/interfaces/colorbalance.h> +#include <gst/interfaces/propertyprobe.h> +/* Helper functions */ +#include <gst/video/video.h> + +#include <bmm_lib.h> +#include <unistd.h> + +#include "drm-xvimage.h" + +/* Object header */ +#include "xvboimagesink.h" + +/* Debugging category */ +#include <gst/gstinfo.h> +GST_DEBUG_CATEGORY_STATIC (gst_debug_xvboimagesink); +#define GST_CAT_DEFAULT gst_debug_xvboimagesink +GST_DEBUG_CATEGORY_STATIC (GST_CAT_PERFORMANCE); + +typedef struct +{ + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long input_mode; + unsigned long status; +} +MotifWmHints, MwmHints; + +static volatile unsigned int viewable = 2; +static volatile unsigned int gst_obscured = 0; + +/* Check deinterlace flag defined by IPP */ +#define IPPGST_BUFFER_CUSTOMDATA(buf) (((GstBuffer*)buf)->_gst_reserved[sizeof(((GstBuffer*)buf)->_gst_reserved)/sizeof(((GstBuffer*)buf)->_gst_reserved[0]) - 1]) + +#define MWM_HINTS_DECORATIONS (1L << 1) + +static void gst_xvboimagesink_reset (GstXvboImageSink * xvimagesink); + +static GstBufferClass *xvimage_buffer_parent_class = NULL; +static void gst_xvboimage_buffer_finalize (GstXvboImageBuffer * xvimage); + +static void gst_xvboimagesink_xwindow_update_geometry (GstXvboImageSink * + xvimagesink, GstXWindow * xwindow); +static gint gst_xvboimagesink_get_format_from_caps (GstXvboImageSink * xvimagesink, + GstCaps * caps); +static void gst_xvboimagesink_expose (GstXOverlay * overlay); + +/* ElementFactory information */ +static const GstElementDetails gst_xvboimagesink_details = +GST_ELEMENT_DETAILS ("Video sink", + "Sink/Video", + "A Xv based videosink", + "Julien Moutte <julien@moutte.net>"); + +/* Default template - initiated with class struct to allow gst-register to work + without X running */ +static GstStaticPadTemplate gst_xvboimagesink_sink_template_factory = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-raw-rgb, " + "framerate = (fraction) [ 0, MAX ], " + "width = (int) [ 1, MAX ], " + "height = (int) [ 1, MAX ]; " + "video/x-raw-yuv, " + "framerate = (fraction) [ 0, MAX ], " + "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]") + ); + +enum +{ + ARG_0, + ARG_CONTRAST, + ARG_BRIGHTNESS, + ARG_HUE, + ARG_SATURATION, + ARG_DISPLAY, + ARG_SYNCHRONOUS, + ARG_PIXEL_ASPECT_RATIO, + ARG_FORCE_ASPECT_RATIO, + ARG_HANDLE_EVENTS, + ARG_DEVICE, + ARG_DEVICE_NAME, + ARG_HANDLE_EXPOSE, + ARG_DOUBLE_BUFFER, + ARG_AUTOPAINT_COLORKEY, + ARG_COLORKEY, + ARG_DRAW_BORDERS +}; + +static GstVideoSinkClass *parent_class = NULL; + +/* ============================================================= */ +/* */ +/* Private Methods */ +/* */ +/* ============================================================= */ + +/* xvimage buffers */ + +#define GST_TYPE_XVIMAGE_BUFFER (gst_xvboimage_buffer_get_type()) + +#define GST_IS_XVIMAGE_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_XVIMAGE_BUFFER)) +#define GST_XVIMAGE_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_XVIMAGE_BUFFER, GstXvboImageBuffer)) +#define GST_XVIMAGE_BUFFER_CAST(obj) ((GstXvboImageBuffer *)(obj)) + +static int +gst_xvboimagesink_import(GstXvboImageSink *xvimagesink, + GstXvboImageBuffer *xvbuf, GstBuffer *buf, int fd) +{ + uint32_t name, handle, *p; + int ret; + + /* Import the dmabuf into DRM */ + ret = drmPrimeFDToHandle(xvimagesink->drm->fd, fd, &handle); + if (ret) + return -1; + + /* Export the DMA buffer as a global object */ + ret = drm_handle_flink(xvimagesink->drm, handle, &name); + if (ret) { + drm_handle_close(xvimagesink->drm, handle); + return -1; + } + + xvbuf->xvbo_handle = handle; + xvbuf->xvbo_buffer = buf; + xvbuf->is_xvbo = TRUE; + xvbuf->bmm_dmabuf_fd = fd; + gst_buffer_ref(buf); + + /* XvBO passing - pass the real image format and DRM buffer object + global name to the X server */ + p = (uint32_t *)xvbuf->xvbo_xvimage->data; + p[0] = xvbuf->im_format; + p[1] = name; + return 0; +} + +static void +gst_xvboimagesink_import_cleanup(GstXvboImageSink *xvimagesink, + GstXvboImageBuffer *xvbuf) +{ + if (xvbuf->xvbo_handle) { + /* The kernel internally holds a reference to the buffer objects + it cares about (which may just be a descriptor pointing at the + bmm buffer). */ + drm_handle_close(xvimagesink->drm, xvbuf->xvbo_handle); + xvbuf->xvbo_handle = 0; + } + xvbuf->is_xvbo = FALSE; +} + +static void +gst_xvboimagesink_drm_reinit(GstXvboImageSink * xvimagesink) +{ + if (xvimagesink->drm) { + unsigned i; + xvimagesink->drm_ring_idx = 0; + for (i = 0; i < NR_DRM_RING; i++) + if (xvimagesink->drm_ring[i]) { + gst_buffer_unref(xvimagesink->drm_ring[i]); + xvimagesink->drm_ring[i] = NULL; + } + } +} + +static void +gst_xvboimagesink_init_xvboimage(GstXvboImageSink * xvimagesink, GstXvboImageBuffer *xvimage) +{ + xvimage->xvbo_xvimage = XvShmCreateImage (xvimagesink->xcontext->disp, + xvimagesink->xcontext->xv_port_id, + GST_MAKE_FOURCC ('X', 'V', 'B', 'O'), NULL, + xvimage->width, xvimage->height, &xvimage->SHMInfo); + xvimage->bmm_dmabuf_fd = -1; +} + +/* This function destroys a GstXvImage handling XShm availability */ +static void +gst_xvboimage_buffer_destroy (GstXvboImageBuffer * xvimage) +{ + GstXvboImageSink *xvimagesink; + + GST_DEBUG_OBJECT (xvimage, "Destroying buffer"); + + xvimagesink = xvimage->xvimagesink; + if (G_UNLIKELY (xvimagesink == NULL)) + goto no_sink; + + g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink)); + + GST_OBJECT_LOCK (xvimagesink); + + /* If the destroyed image is the current one we destroy our reference too */ + if (xvimagesink->cur_image == xvimage) + xvimagesink->cur_image = NULL; + + /* We might have some buffers destroyed after changing state to NULL */ + if (xvimagesink->xcontext == NULL) { + GST_DEBUG_OBJECT (xvimagesink, "Destroying XvImage after Xcontext"); +#ifdef HAVE_XSHM + /* Need to free the shared memory segment even if the x context + * was already cleaned up */ + if (xvimage->SHMInfo.shmaddr != ((void *) -1)) { + shmdt (xvimage->SHMInfo.shmaddr); + } +#endif + goto beach; + } + + g_mutex_lock (xvimagesink->x_lock); + +#ifdef HAVE_XSHM + if (xvimagesink->xcontext->use_xshm) { + if (xvimage->SHMInfo.shmaddr != ((void *) -1)) { + GST_DEBUG_OBJECT (xvimagesink, "XServer ShmDetaching from 0x%x id 0x%lx", + xvimage->SHMInfo.shmid, xvimage->SHMInfo.shmseg); + XShmDetach (xvimagesink->xcontext->disp, &xvimage->SHMInfo); + XSync (xvimagesink->xcontext->disp, FALSE); + + shmdt (xvimage->SHMInfo.shmaddr); + } + if (xvimage->xvimage) + XFree (xvimage->xvimage); + } else +#endif /* HAVE_XSHM */ + { + if (xvimage->xvimage) { + if (xvimage->xvimage->data) { + g_free (xvimage->xvimage->data); + } + XFree (xvimage->xvimage); + } + } + + gst_xvboimagesink_import_cleanup(xvimagesink, xvimage); + if (xvimage->xvbo_buffer) + gst_buffer_unref(xvimage->xvbo_buffer); + if (xvimage->xvbo_xvimage) + XFree (xvimage->xvbo_xvimage); + + XSync (xvimagesink->xcontext->disp, FALSE); + + g_mutex_unlock (xvimagesink->x_lock); + +beach: + GST_OBJECT_UNLOCK (xvimagesink); + xvimage->xvimagesink = NULL; + gst_object_unref (xvimagesink); + + GST_MINI_OBJECT_CLASS (xvimage_buffer_parent_class)->finalize (GST_MINI_OBJECT + (xvimage)); + + return; + +no_sink: + { + GST_WARNING ("no sink found"); + return; + } +} + +static void +gst_xvboimage_buffer_finalize (GstXvboImageBuffer * xvimage) +{ + GstXvboImageSink *xvimagesink; + gboolean running; + + xvimagesink = xvimage->xvimagesink; + if (G_UNLIKELY (xvimagesink == NULL)) + goto no_sink; + + g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink)); + + GST_OBJECT_LOCK (xvimagesink); + running = xvimagesink->running; + GST_OBJECT_UNLOCK (xvimagesink); + + /* If our geometry changed we can't reuse that image. */ + if (running == FALSE) { + GST_LOG_OBJECT (xvimage, "destroy image as sink is shutting down"); + gst_xvboimage_buffer_destroy (xvimage); + } else if ((xvimage->width != xvimagesink->video_width) || + (xvimage->height != xvimagesink->video_height)) { + GST_LOG_OBJECT (xvimage, + "destroy image as its size changed %dx%d vs current %dx%d", + xvimage->width, xvimage->height, + xvimagesink->video_width, xvimagesink->video_height); + gst_xvboimage_buffer_destroy (xvimage); + } else { + /* In that case we can reuse the image and add it to our image pool. */ + GST_LOG_OBJECT (xvimage, "recycling image in pool"); + /* need to increment the refcount again to recycle */ + gst_buffer_ref (GST_BUFFER_CAST (xvimage)); + g_mutex_lock (xvimagesink->pool_lock); + xvimagesink->image_pool = g_slist_prepend (xvimagesink->image_pool, + xvimage); + g_mutex_unlock (xvimagesink->pool_lock); + } + return; + +no_sink: + { + GST_WARNING ("no sink found"); + return; + } +} + +static void +gst_xvboimage_buffer_free (GstXvboImageBuffer * xvimage) +{ + /* make sure it is not recycled */ + xvimage->width = -1; + xvimage->height = -1; + gst_buffer_unref (GST_BUFFER (xvimage)); +} + +static void +gst_xvboimage_buffer_init (GstXvboImageBuffer * xvimage, gpointer g_class) +{ +#ifdef HAVE_XSHM + xvimage->SHMInfo.shmaddr = ((void *) -1); + xvimage->SHMInfo.shmid = -1; +#endif +} + +static void +gst_xvboimage_buffer_class_init (gpointer g_class, gpointer class_data) +{ + GstMiniObjectClass *mini_object_class = GST_MINI_OBJECT_CLASS (g_class); + + xvimage_buffer_parent_class = g_type_class_peek_parent (g_class); + + mini_object_class->finalize = (GstMiniObjectFinalizeFunction) + gst_xvboimage_buffer_finalize; +} + +static GType +gst_xvboimage_buffer_get_type (void) +{ + static GType _gst_xvboimage_buffer_type; + + if (G_UNLIKELY (_gst_xvboimage_buffer_type == 0)) { + static const GTypeInfo xvimage_buffer_info = { + sizeof (GstBufferClass), + NULL, + NULL, + gst_xvboimage_buffer_class_init, + NULL, + NULL, + sizeof (GstXvboImageBuffer), + 0, + (GInstanceInitFunc) gst_xvboimage_buffer_init, + NULL + }; + _gst_xvboimage_buffer_type = g_type_register_static (GST_TYPE_BUFFER, + "GstXvboImageBuffer", &xvimage_buffer_info, 0); + } + return _gst_xvboimage_buffer_type; +} + +/* X11 stuff */ + +static gboolean error_caught = FALSE; + +static int +gst_xvboimagesink_handle_xerror (Display * display, XErrorEvent * xevent) +{ + char error_msg[1024]; + + XGetErrorText (display, xevent->error_code, error_msg, 1024); + GST_DEBUG ("xvimagesink triggered an XError. error: %s", error_msg); + error_caught = TRUE; + return 0; +} + +#ifdef HAVE_XSHM +/* This function checks that it is actually really possible to create an image + using XShm */ +static gboolean +gst_xvboimagesink_check_xshm_calls (GstXContext * xcontext) +{ + XvImage *xvimage; + XShmSegmentInfo SHMInfo; + gint size; + int (*handler) (Display *, XErrorEvent *); + gboolean result = FALSE; + gboolean did_attach = FALSE; + + g_return_val_if_fail (xcontext != NULL, FALSE); + + /* Sync to ensure any older errors are already processed */ + XSync (xcontext->disp, FALSE); + + /* Set defaults so we don't free these later unnecessarily */ + SHMInfo.shmaddr = ((void *) -1); + SHMInfo.shmid = -1; + + /* Setting an error handler to catch failure */ + error_caught = FALSE; + handler = XSetErrorHandler (gst_xvboimagesink_handle_xerror); + + /* Trying to create a 1x1 picture */ + GST_DEBUG ("XvShmCreateImage of 1x1"); + xvimage = XvShmCreateImage (xcontext->disp, xcontext->xv_port_id, + xcontext->im_format, NULL, 1, 1, &SHMInfo); + + /* Might cause an error, sync to ensure it is noticed */ + XSync (xcontext->disp, FALSE); + if (!xvimage || error_caught) { + GST_WARNING ("could not XvShmCreateImage a 1x1 image"); + goto beach; + } + size = xvimage->data_size; + + SHMInfo.shmid = shmget (IPC_PRIVATE, size, IPC_CREAT | 0777); + if (SHMInfo.shmid == -1) { + GST_WARNING ("could not get shared memory of %d bytes", size); + goto beach; + } + + SHMInfo.shmaddr = shmat (SHMInfo.shmid, NULL, 0); + if (SHMInfo.shmaddr == ((void *) -1)) { + GST_WARNING ("Failed to shmat: %s", g_strerror (errno)); + /* Clean up the shared memory segment */ + shmctl (SHMInfo.shmid, IPC_RMID, NULL); + goto beach; + } + + xvimage->data = SHMInfo.shmaddr; + SHMInfo.readOnly = FALSE; + + if (XShmAttach (xcontext->disp, &SHMInfo) == 0) { + GST_WARNING ("Failed to XShmAttach"); + /* Clean up the shared memory segment */ + shmctl (SHMInfo.shmid, IPC_RMID, NULL); + goto beach; + } + + /* Sync to ensure we see any errors we caused */ + XSync (xcontext->disp, FALSE); + + /* Delete the shared memory segment as soon as everyone is attached. + * This way, it will be deleted as soon as we detach later, and not + * leaked if we crash. */ + shmctl (SHMInfo.shmid, IPC_RMID, NULL); + + if (!error_caught) { + GST_DEBUG ("XServer ShmAttached to 0x%x, id 0x%lx", SHMInfo.shmid, + SHMInfo.shmseg); + + did_attach = TRUE; + /* store whether we succeeded in result */ + result = TRUE; + } else { + GST_WARNING ("MIT-SHM extension check failed at XShmAttach. " + "Not using shared memory."); + } + +beach: + /* Sync to ensure we swallow any errors we caused and reset error_caught */ + XSync (xcontext->disp, FALSE); + + error_caught = FALSE; + XSetErrorHandler (handler); + + if (did_attach) { + GST_DEBUG ("XServer ShmDetaching from 0x%x id 0x%lx", + SHMInfo.shmid, SHMInfo.shmseg); + XShmDetach (xcontext->disp, &SHMInfo); + XSync (xcontext->disp, FALSE); + } + if (SHMInfo.shmaddr != ((void *) -1)) + shmdt (SHMInfo.shmaddr); + if (xvimage) + XFree (xvimage); + return result; +} +#endif /* HAVE_XSHM */ + +/* This function handles GstXvImage creation depending on XShm availability */ +static GstXvboImageBuffer * +gst_xvboimagesink_xvimage_new (GstXvboImageSink * xvimagesink, GstCaps * caps) +{ + GstXvboImageBuffer *xvimage = NULL; + GstStructure *structure = NULL; + gboolean succeeded = FALSE; + int (*handler) (Display *, XErrorEvent *); + + g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL); + + if (caps == NULL) + return NULL; + + xvimage = (GstXvboImageBuffer *) gst_mini_object_new (GST_TYPE_XVIMAGE_BUFFER); + GST_DEBUG_OBJECT (xvimage, "Creating new XvImageBuffer"); + + structure = gst_caps_get_structure (caps, 0); + + if (!gst_structure_get_int (structure, "width", &xvimage->width) || + !gst_structure_get_int (structure, "height", &xvimage->height)) { + GST_WARNING ("failed getting geometry from caps %" GST_PTR_FORMAT, caps); + } + + GST_LOG_OBJECT (xvimagesink, "creating %dx%d", xvimage->width, + xvimage->height); + + xvimage->im_format = gst_xvboimagesink_get_format_from_caps (xvimagesink, caps); + if (xvimage->im_format == -1) { + GST_WARNING_OBJECT (xvimagesink, "failed to get format from caps %" + GST_PTR_FORMAT, caps); + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE, + ("Failed to create output image buffer of %dx%d pixels", + xvimage->width, xvimage->height), ("Invalid input caps")); + goto beach_unlocked; + } + xvimage->xvimagesink = gst_object_ref (xvimagesink); + + g_mutex_lock (xvimagesink->x_lock); + + /* Setting an error handler to catch failure */ + error_caught = FALSE; + handler = XSetErrorHandler (gst_xvboimagesink_handle_xerror); + +#ifdef HAVE_XSHM + if (xvimagesink->xcontext->use_xshm) { + int expected_size; + + xvimage->xvimage = XvShmCreateImage (xvimagesink->xcontext->disp, + xvimagesink->xcontext->xv_port_id, + xvimage->im_format, NULL, + xvimage->width, xvimage->height, &xvimage->SHMInfo); + if (!xvimage->xvimage || error_caught) { + g_mutex_unlock (xvimagesink->x_lock); + /* Reset error handler */ + error_caught = FALSE; + XSetErrorHandler (handler); + /* Push an error */ + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE, + ("Failed to create output image buffer of %dx%d pixels", + xvimage->width, xvimage->height), + ("could not XvShmCreateImage a %dx%d image", + xvimage->width, xvimage->height)); + goto beach_unlocked; + } + + /* we have to use the returned data_size for our shm size */ + xvimage->size = xvimage->xvimage->data_size; + GST_LOG_OBJECT (xvimagesink, "XShm image size is %" G_GSIZE_FORMAT, + xvimage->size); + + /* If we have xvbo, we want the xvbo image format here */ + gst_xvboimagesink_init_xvboimage(xvimagesink, xvimage); + + /* calculate the expected size. This is only for sanity checking the + * number we get from X. */ + switch (xvimage->im_format) { + case GST_MAKE_FOURCC ('I', '4', '2', '0'): + case GST_MAKE_FOURCC ('Y', 'V', '1', '2'): + { + gint pitches[3]; + gint offsets[3]; + guint plane; + + offsets[0] = 0; + pitches[0] = GST_ROUND_UP_4 (xvimage->width); + offsets[1] = offsets[0] + pitches[0] * GST_ROUND_UP_2 (xvimage->height); + pitches[1] = GST_ROUND_UP_8 (xvimage->width) / 2; + offsets[2] = + offsets[1] + pitches[1] * GST_ROUND_UP_2 (xvimage->height) / 2; + pitches[2] = GST_ROUND_UP_8 (pitches[0]) / 2; + + expected_size = + offsets[2] + pitches[2] * GST_ROUND_UP_2 (xvimage->height) / 2; + + for (plane = 0; plane < xvimage->xvimage->num_planes; plane++) { + GST_DEBUG_OBJECT (xvimagesink, + "Plane %u has a expected pitch of %d bytes, " "offset of %d", + plane, pitches[plane], offsets[plane]); + } + break; + } + case GST_MAKE_FOURCC ('I', '4', '2', '2'): + case GST_MAKE_FOURCC ('Y', 'V', '1', '6'): + { + gint pitches[3]; + gint offsets[3]; + guint plane; + + offsets[0] = 0; + pitches[0] = GST_ROUND_UP_4 (xvimage->width); + offsets[1] = offsets[0] + pitches[0] * xvimage->height; + pitches[1] = GST_ROUND_UP_8 (xvimage->width) / 2; + offsets[2] = offsets[1] + pitches[1] * xvimage->height; + pitches[2] = GST_ROUND_UP_8 (pitches[0]) / 2; + + expected_size = offsets[2] + pitches[2] * xvimage->height; + + for (plane = 0; plane < xvimage->xvimage->num_planes; plane++) { + GST_DEBUG_OBJECT (xvimagesink, + "Plane %u has a expected pitch of %d bytes, " "offset of %d", + plane, pitches[plane], offsets[plane]); + } + break; + } + case GST_MAKE_FOURCC ('Y', 'U', 'Y', '2'): + case GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'): + expected_size = xvimage->height * GST_ROUND_UP_4 (xvimage->width * 2); + break; + default: + expected_size = 0; + break; + } + if (expected_size != 0 && xvimage->size != expected_size) { + GST_WARNING_OBJECT (xvimagesink, + "unexpected XShm image size (got %" G_GSIZE_FORMAT ", expected %d)", + xvimage->size, expected_size); + } + + /* Be verbose about our XvImage stride */ + { + guint plane; + + for (plane = 0; plane < xvimage->xvimage->num_planes; plane++) { + GST_DEBUG_OBJECT (xvimagesink, "Plane %u has a pitch of %d bytes, " + "offset of %d", plane, xvimage->xvimage->pitches[plane], + xvimage->xvimage->offsets[plane]); + } + } + + xvimage->SHMInfo.shmid = shmget (IPC_PRIVATE, xvimage->size, + IPC_CREAT | 0777); + if (xvimage->SHMInfo.shmid == -1) { + g_mutex_unlock (xvimagesink->x_lock); + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE, + ("Failed to create output image buffer of %dx%d pixels", + xvimage->width, xvimage->height), + ("could not get shared memory of %" G_GSIZE_FORMAT " bytes", + xvimage->size)); + goto beach_unlocked; + } + + xvimage->SHMInfo.shmaddr = shmat (xvimage->SHMInfo.shmid, NULL, 0); + if (xvimage->SHMInfo.shmaddr == ((void *) -1)) { + g_mutex_unlock (xvimagesink->x_lock); + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE, + ("Failed to create output image buffer of %dx%d pixels", + xvimage->width, xvimage->height), + ("Failed to shmat: %s", g_strerror (errno))); + /* Clean up the shared memory segment */ + shmctl (xvimage->SHMInfo.shmid, IPC_RMID, NULL); + goto beach_unlocked; + } + + xvimage->xvimage->data = xvimage->SHMInfo.shmaddr; + if (xvimage->xvbo_xvimage) + xvimage->xvbo_xvimage->data = xvimage->SHMInfo.shmaddr; + + xvimage->SHMInfo.readOnly = FALSE; + + if (XShmAttach (xvimagesink->xcontext->disp, &xvimage->SHMInfo) == 0) { + /* Clean up the shared memory segment */ + shmctl (xvimage->SHMInfo.shmid, IPC_RMID, NULL); + + g_mutex_unlock (xvimagesink->x_lock); + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE, + ("Failed to create output image buffer of %dx%d pixels", + xvimage->width, xvimage->height), ("Failed to XShmAttach")); + goto beach_unlocked; + } + + XSync (xvimagesink->xcontext->disp, FALSE); + + /* Delete the shared memory segment as soon as we everyone is attached. + * This way, it will be deleted as soon as we detach later, and not + * leaked if we crash. */ + shmctl (xvimage->SHMInfo.shmid, IPC_RMID, NULL); + + GST_DEBUG_OBJECT (xvimagesink, "XServer ShmAttached to 0x%x, id 0x%lx", + xvimage->SHMInfo.shmid, xvimage->SHMInfo.shmseg); + } else +#endif /* HAVE_XSHM */ + { + xvimage->xvimage = XvCreateImage (xvimagesink->xcontext->disp, + xvimagesink->xcontext->xv_port_id, + xvimage->im_format, NULL, xvimage->width, xvimage->height); + if (!xvimage->xvimage || error_caught) { + g_mutex_unlock (xvimagesink->x_lock); + /* Reset error handler */ + error_caught = FALSE; + XSetErrorHandler (handler); + /* Push an error */ + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE, + ("Failed to create outputimage buffer of %dx%d pixels", + xvimage->width, xvimage->height), + ("could not XvCreateImage a %dx%d image", + xvimage->width, xvimage->height)); + goto beach_unlocked; + } + + /* we have to use the returned data_size for our image size */ + xvimage->size = xvimage->xvimage->data_size; + xvimage->xvimage->data = g_malloc (xvimage->size); + + XSync (xvimagesink->xcontext->disp, FALSE); + } + + /* Reset error handler */ + error_caught = FALSE; + XSetErrorHandler (handler); + + succeeded = TRUE; + + GST_BUFFER_DATA (xvimage) = (guchar *) xvimage->xvimage->data; + GST_BUFFER_SIZE (xvimage) = xvimage->size; + + g_mutex_unlock (xvimagesink->x_lock); + +beach_unlocked: + if (!succeeded) { + gst_xvboimage_buffer_free (xvimage); + xvimage = NULL; + } + + return xvimage; +} + +/* We are called with the x_lock taken */ +static void +gst_xvboimagesink_xwindow_draw_borders (GstXvboImageSink * xvimagesink, + GstXWindow * xwindow, GstVideoRectangle rect) +{ + g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink)); + g_return_if_fail (xwindow != NULL); + + XSetForeground (xvimagesink->xcontext->disp, xwindow->gc, + xvimagesink->xcontext->black); + + /* Left border */ + if (rect.x > 0) { + XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc, + 0, 0, rect.x, xwindow->height); + } + + /* Right border */ + if ((rect.x + rect.w) < xwindow->width) { + XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc, + rect.x + rect.w, 0, xwindow->width, xwindow->height); + } + + /* Top border */ + if (rect.y > 0) { + XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc, + 0, 0, xwindow->width, rect.y); + } + + /* Bottom border */ + if ((rect.y + rect.h) < xwindow->height) { + XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc, + 0, rect.y + rect.h, xwindow->width, xwindow->height); + } +} + +/* This function puts a GstXvImage on a GstXvboImageSink's window. Returns FALSE + * if no window was available */ +static gboolean +gst_xvboimagesink_xvimage_put (GstXvboImageSink * xvimagesink, + GstXvboImageBuffer * xvimage) +{ + GstVideoRectangle src, dst, result; + gboolean draw_border = FALSE; + XvImage *image; + + /* We take the flow_lock. If expose is in there we don't want to run + concurrently from the data flow thread */ + g_mutex_lock (xvimagesink->flow_lock); + + if (G_UNLIKELY (xvimagesink->xwindow == NULL)) { + g_mutex_unlock (xvimagesink->flow_lock); + return FALSE; + } + + /* Draw borders when displaying the first frame. After this + draw borders only on expose event or after a size change. */ + if (!xvimagesink->cur_image || xvimagesink->redraw_border) { + draw_border = TRUE; + } + + /* Store a reference to the last image we put, lose the previous one */ + if (xvimage && xvimagesink->cur_image != xvimage) { + if (xvimagesink->cur_image) { + GST_LOG_OBJECT (xvimagesink, "unreffing %p", xvimagesink->cur_image); + gst_buffer_unref (GST_BUFFER_CAST (xvimagesink->cur_image)); + } + GST_LOG_OBJECT (xvimagesink, "reffing %p as our current image", xvimage); + xvimagesink->cur_image = + GST_XVIMAGE_BUFFER_CAST (gst_buffer_ref (GST_BUFFER_CAST (xvimage))); + } + + /* Expose sends a NULL image, we take the latest frame */ + if (!xvimage) { + if (xvimagesink->cur_image) { + draw_border = TRUE; + xvimage = xvimagesink->cur_image; + } else { + g_mutex_unlock (xvimagesink->flow_lock); + return TRUE; + } + } + + gst_xvboimagesink_xwindow_update_geometry (xvimagesink, xvimagesink->xwindow); + + /* Do nothing if the window is invisible */ + if((viewable < 2) || (gst_obscured == 1)) + goto out; + + /* We use the calculated geometry from _setcaps as a source to respect + source and screen pixel aspect ratios. */ + src.w = GST_VIDEO_SINK_WIDTH (xvimagesink); + src.h = GST_VIDEO_SINK_HEIGHT (xvimagesink); + dst.w = xvimagesink->xwindow->width; + dst.h = xvimagesink->xwindow->height; + + if (xvimagesink->keep_aspect) { + gst_video_sink_center_rect (src, dst, &result, TRUE); + } else { + result.x = result.y = 0; + result.w = dst.w; + result.h = dst.h; + } + + g_mutex_lock (xvimagesink->x_lock); + + if (draw_border && xvimagesink->draw_borders) { + gst_xvboimagesink_xwindow_draw_borders (xvimagesink, xvimagesink->xwindow, + result); + xvimagesink->redraw_border = FALSE; + } + + /* Select the Xv image we're going to use for this buffer. If we were + able to map it via XVBO, we must use the XVBO Xv image, otherwise + we use its native Xv image. Argh, should not use the same identifier + (xvimage) for two different things! */ + image = xvimage->xvimage; + if (xvimage->xvbo_buffer) + image = xvimage->xvbo_xvimage; + +#ifdef HAVE_XSHM + if (xvimagesink->xcontext->use_xshm) { + /* We scale to the window's geometry */ + GST_LOG_OBJECT (xvimagesink, + "XvShmPutImage with image %dx%d and window %dx%d, from xvimage %" + GST_PTR_FORMAT, + xvimage->width, xvimage->height, + xvimagesink->xwindow->width, xvimagesink->xwindow->height, image); + + XvShmPutImage (xvimagesink->xcontext->disp, + xvimagesink->xcontext->xv_port_id, + xvimagesink->xwindow->win, + xvimagesink->xwindow->gc, image, + xvimagesink->disp_x, xvimagesink->disp_y, + xvimagesink->disp_width, xvimagesink->disp_height, + result.x, result.y, result.w, result.h, FALSE); + } else +#endif /* HAVE_XSHM */ + { + /* We scale to the window's geometry */ + XvPutImage (xvimagesink->xcontext->disp, + xvimagesink->xcontext->xv_port_id, + xvimagesink->xwindow->win, + xvimagesink->xwindow->gc, image, + xvimagesink->disp_x, xvimagesink->disp_y, + xvimagesink->disp_width, xvimagesink->disp_height, + result.x, result.y, result.w, result.h); + } + + XSync (xvimagesink->xcontext->disp, FALSE); + + g_mutex_unlock (xvimagesink->x_lock); + +out: + g_mutex_unlock (xvimagesink->flow_lock); + + return TRUE; +} + +static gboolean +gst_xvboimagesink_xwindow_decorate (GstXvboImageSink * xvimagesink, + GstXWindow * window) +{ + Atom hints_atom = None; + MotifWmHints *hints; + + g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), FALSE); + g_return_val_if_fail (window != NULL, FALSE); + + g_mutex_lock (xvimagesink->x_lock); + + hints_atom = XInternAtom (xvimagesink->xcontext->disp, "_MOTIF_WM_HINTS", + True); + if (hints_atom == None) { + g_mutex_unlock (xvimagesink->x_lock); + return FALSE; + } + + hints = g_malloc0 (sizeof (MotifWmHints)); + + hints->flags |= MWM_HINTS_DECORATIONS; + hints->decorations = 1 << 0; + + XChangeProperty (xvimagesink->xcontext->disp, window->win, + hints_atom, hints_atom, 32, PropModeReplace, + (guchar *) hints, sizeof (MotifWmHints) / sizeof (long)); + + XSync (xvimagesink->xcontext->disp, FALSE); + + g_mutex_unlock (xvimagesink->x_lock); + + g_free (hints); + + return TRUE; +} + +static void +gst_xvboimagesink_xwindow_set_title (GstXvboImageSink * xvimagesink, + GstXWindow * xwindow, const gchar * media_title) +{ + if (media_title) { + g_free (xvimagesink->media_title); + xvimagesink->media_title = g_strdup (media_title); + } + if (xwindow) { + /* we have a window */ + if (xwindow->internal) { + XTextProperty xproperty; + const gchar *app_name; + const gchar *title = NULL; + gchar *title_mem = NULL; + + /* set application name as a title */ + app_name = g_get_application_name (); + + if (app_name && xvimagesink->media_title) { + title = title_mem = g_strconcat (xvimagesink->media_title, " : ", + app_name, NULL); + } else if (app_name) { + title = app_name; + } else if (xvimagesink->media_title) { + title = xvimagesink->media_title; + } + + if (title) { + if ((XStringListToTextProperty (((char **) &title), 1, + &xproperty)) != 0) { + XSetWMName (xvimagesink->xcontext->disp, xwindow->win, &xproperty); + XFree (xproperty.value); + } + + g_free (title_mem); + } + } + } +} + +/* This function handles a GstXWindow creation + * The width and height are the actual pixel size on the display */ +static GstXWindow * +gst_xvboimagesink_xwindow_new (GstXvboImageSink * xvimagesink, + gint width, gint height) +{ + GstXWindow *xwindow = NULL; + XGCValues values; + + g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL); + + xwindow = g_new0 (GstXWindow, 1); + + xwindow->width = width; + xwindow->height = height; + xwindow->internal = TRUE; + + g_mutex_lock (xvimagesink->x_lock); + + xwindow->win = XCreateSimpleWindow (xvimagesink->xcontext->disp, + xvimagesink->xcontext->root, + 0, 0, xwindow->width, xwindow->height, + 0, 0, xvimagesink->xcontext->black); + + /* We have to do that to prevent X from redrawing the background on + * ConfigureNotify. This takes away flickering of video when resizing. */ + XSetWindowBackgroundPixmap (xvimagesink->xcontext->disp, xwindow->win, None); + + /* set application name as a title */ + gst_xvboimagesink_xwindow_set_title (xvimagesink, xwindow, NULL); + + if (xvimagesink->handle_events) { + Atom wm_delete; + + XSelectInput (xvimagesink->xcontext->disp, xwindow->win, ExposureMask | + StructureNotifyMask | PointerMotionMask | KeyPressMask | + KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | VisibilityChangeMask); + + /* Tell the window manager we'd like delete client messages instead of + * being killed */ + wm_delete = XInternAtom (xvimagesink->xcontext->disp, + "WM_DELETE_WINDOW", True); + if (wm_delete != None) { + (void) XSetWMProtocols (xvimagesink->xcontext->disp, xwindow->win, + &wm_delete, 1); + } + } + + xwindow->gc = XCreateGC (xvimagesink->xcontext->disp, + xwindow->win, 0, &values); + + XMapRaised (xvimagesink->xcontext->disp, xwindow->win); + + XSync (xvimagesink->xcontext->disp, FALSE); + + g_mutex_unlock (xvimagesink->x_lock); + + gst_xvboimagesink_xwindow_decorate (xvimagesink, xwindow); + + gst_x_overlay_got_xwindow_id (GST_X_OVERLAY (xvimagesink), xwindow->win); + + return xwindow; +} + +/* This function destroys a GstXWindow */ +static void +gst_xvboimagesink_xwindow_destroy (GstXvboImageSink * xvimagesink, + GstXWindow * xwindow) +{ + g_return_if_fail (xwindow != NULL); + g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink)); + + g_mutex_lock (xvimagesink->x_lock); + + /* If we did not create that window we just free the GC and let it live */ + if (xwindow->internal) + XDestroyWindow (xvimagesink->xcontext->disp, xwindow->win); + else + XSelectInput (xvimagesink->xcontext->disp, xwindow->win, 0); + + XFreeGC (xvimagesink->xcontext->disp, xwindow->gc); + + XSync (xvimagesink->xcontext->disp, FALSE); + + g_mutex_unlock (xvimagesink->x_lock); + + g_free (xwindow); +} + +static void +gst_xvboimagesink_xwindow_update_geometry (GstXvboImageSink * xvimagesink, + GstXWindow * xwindow) +{ + XWindowAttributes attr; + + g_return_if_fail (xwindow != NULL); + g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink)); + + /* Update the window geometry */ + g_mutex_lock (xvimagesink->x_lock); + + XGetWindowAttributes (xvimagesink->xcontext->disp, + xvimagesink->xwindow->win, &attr); + + viewable = attr.map_state; + + xvimagesink->xwindow->width = attr.width; + xvimagesink->xwindow->height = attr.height; + + g_mutex_unlock (xvimagesink->x_lock); +} + +static void +gst_xvboimagesink_xwindow_clear (GstXvboImageSink * xvimagesink, + GstXWindow * xwindow) +{ + g_return_if_fail (xwindow != NULL); + g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink)); + + g_mutex_lock (xvimagesink->x_lock); + + XvStopVideo (xvimagesink->xcontext->disp, xvimagesink->xcontext->xv_port_id, + xwindow->win); + + XSetForeground (xvimagesink->xcontext->disp, xwindow->gc, + xvimagesink->xcontext->black); + + XFillRectangle (xvimagesink->xcontext->disp, xwindow->win, xwindow->gc, + 0, 0, xwindow->width, xwindow->height); + + XSync (xvimagesink->xcontext->disp, FALSE); + + g_mutex_unlock (xvimagesink->x_lock); +} + +/* This function commits our internal colorbalance settings to our grabbed Xv + port. If the xcontext is not initialized yet it simply returns */ +static void +gst_xvboimagesink_update_colorbalance (GstXvboImageSink * xvimagesink) +{ + GList *channels = NULL; + + g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink)); + + /* If we haven't initialized the X context we can't update anything */ + if (xvimagesink->xcontext == NULL) + return; + + /* Don't set the attributes if they haven't been changed, to avoid + * rounding errors changing the values */ + if (!xvimagesink->cb_changed) + return; + + /* For each channel of the colorbalance we calculate the correct value + doing range conversion and then set the Xv port attribute to match our + values. */ + channels = xvimagesink->xcontext->channels_list; + + while (channels) { + if (channels->data && GST_IS_COLOR_BALANCE_CHANNEL (channels->data)) { + GstColorBalanceChannel *channel = NULL; + Atom prop_atom; + gint value = 0; + gdouble convert_coef; + + channel = GST_COLOR_BALANCE_CHANNEL (channels->data); + g_object_ref (channel); + + /* Our range conversion coef */ + convert_coef = (channel->max_value - channel->min_value) / 2000.0; + + if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) { + value = xvimagesink->hue; + } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) { + value = xvimagesink->saturation; + } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) { + value = xvimagesink->contrast; + } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) { + value = xvimagesink->brightness; + } else { + g_warning ("got an unknown channel %s", channel->label); + g_object_unref (channel); + return; + } + + /* Committing to Xv port */ + g_mutex_lock (xvimagesink->x_lock); + prop_atom = + XInternAtom (xvimagesink->xcontext->disp, channel->label, True); + if (prop_atom != None) { + int xv_value; + xv_value = + floor (0.5 + (value + 1000) * convert_coef + channel->min_value); + XvSetPortAttribute (xvimagesink->xcontext->disp, + xvimagesink->xcontext->xv_port_id, prop_atom, xv_value); + } + g_mutex_unlock (xvimagesink->x_lock); + + g_object_unref (channel); + } + channels = g_list_next (channels); + } +} + +/* This function handles XEvents that might be in the queue. It generates + GstEvent that will be sent upstream in the pipeline to handle interactivity + and navigation. It will also listen for configure events on the window to + trigger caps renegotiation so on the fly software scaling can work. */ +static void +gst_xvboimagesink_handle_xevents (GstXvboImageSink * xvimagesink) +{ + XEvent e; + guint pointer_x = 0, pointer_y = 0; + gboolean pointer_moved = FALSE; + gboolean exposed = FALSE, configured = FALSE; + + g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink)); + + /* Handle Interaction, produces navigation events */ + + /* We get all pointer motion events, only the last position is + interesting. */ + g_mutex_lock (xvimagesink->flow_lock); + g_mutex_lock (xvimagesink->x_lock); + while (XCheckWindowEvent (xvimagesink->xcontext->disp, + xvimagesink->xwindow->win, PointerMotionMask | VisibilityChangeMask, &e)) { + g_mutex_unlock (xvimagesink->x_lock); + g_mutex_unlock (xvimagesink->flow_lock); + + switch (e.type) { + case MotionNotify: + pointer_x = e.xmotion.x; + pointer_y = e.xmotion.y; + pointer_moved = TRUE; + break; + case VisibilityNotify: + { + //printf("recieved VisibilityNotify \n"); + XVisibilityEvent *event_xv = (XVisibilityEvent *) (&e); + if (event_xv->state == VisibilityFullyObscured) { + gst_obscured = 1; + //printf("recieved VisibilityFullyObscured \n"); + } else { + gst_obscured = 0; + } + } + break; + default: + break; + } + g_mutex_lock (xvimagesink->flow_lock); + g_mutex_lock (xvimagesink->x_lock); + } + if (pointer_moved) { + g_mutex_unlock (xvimagesink->x_lock); + g_mutex_unlock (xvimagesink->flow_lock); + + GST_DEBUG ("xvimagesink pointer moved over window at %d,%d", + pointer_x, pointer_y); + gst_navigation_send_mouse_event (GST_NAVIGATION (xvimagesink), + "mouse-move", 0, e.xbutton.x, e.xbutton.y); + + g_mutex_lock (xvimagesink->flow_lock); + g_mutex_lock (xvimagesink->x_lock); + } + + /* We get all events on our window to throw them upstream */ + while (XCheckWindowEvent (xvimagesink->xcontext->disp, + xvimagesink->xwindow->win, + KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask, + &e)) { + KeySym keysym; + + /* We lock only for the X function call */ + g_mutex_unlock (xvimagesink->x_lock); + g_mutex_unlock (xvimagesink->flow_lock); + + switch (e.type) { + case ButtonPress: + /* Mouse button pressed over our window. We send upstream + events for interactivity/navigation */ + GST_DEBUG ("xvimagesink button %d pressed over window at %d,%d", + e.xbutton.button, e.xbutton.x, e.xbutton.y); + gst_navigation_send_mouse_event (GST_NAVIGATION (xvimagesink), + "mouse-button-press", e.xbutton.button, e.xbutton.x, e.xbutton.y); + break; + case ButtonRelease: + /* Mouse button released over our window. We send upstream + events for interactivity/navigation */ + GST_DEBUG ("xvimagesink button %d released over window at %d,%d", + e.xbutton.button, e.xbutton.x, e.xbutton.y); + gst_navigation_send_mouse_event (GST_NAVIGATION (xvimagesink), + "mouse-button-release", e.xbutton.button, e.xbutton.x, e.xbutton.y); + break; + case KeyPress: + case KeyRelease: + /* Key pressed/released over our window. We send upstream + events for interactivity/navigation */ + GST_DEBUG ("xvimagesink key %d pressed over window at %d,%d", + e.xkey.keycode, e.xkey.x, e.xkey.y); + g_mutex_lock (xvimagesink->x_lock); + keysym = XKeycodeToKeysym (xvimagesink->xcontext->disp, + e.xkey.keycode, 0); + g_mutex_unlock (xvimagesink->x_lock); + if (keysym != NoSymbol) { + char *key_str = NULL; + + g_mutex_lock (xvimagesink->x_lock); + key_str = XKeysymToString (keysym); + g_mutex_unlock (xvimagesink->x_lock); + gst_navigation_send_key_event (GST_NAVIGATION (xvimagesink), + e.type == KeyPress ? "key-press" : "key-release", key_str); + } else { + gst_navigation_send_key_event (GST_NAVIGATION (xvimagesink), + e.type == KeyPress ? "key-press" : "key-release", "unknown"); + } + break; + default: + GST_DEBUG ("xvimagesink unhandled X event (%d)", e.type); + } + g_mutex_lock (xvimagesink->flow_lock); + g_mutex_lock (xvimagesink->x_lock); + } + + /* Handle Expose */ + while (XCheckWindowEvent (xvimagesink->xcontext->disp, + xvimagesink->xwindow->win, ExposureMask | StructureNotifyMask, &e)) { + switch (e.type) { + case Expose: + exposed = TRUE; + break; + case ConfigureNotify: + configured = TRUE; + break; + default: + break; + } + } + + if (xvimagesink->handle_expose && (exposed || configured)) { + g_mutex_unlock (xvimagesink->x_lock); + g_mutex_unlock (xvimagesink->flow_lock); + + gst_xvboimagesink_expose (GST_X_OVERLAY (xvimagesink)); + + g_mutex_lock (xvimagesink->flow_lock); + g_mutex_lock (xvimagesink->x_lock); + } + + /* Handle Display events */ + while (XPending (xvimagesink->xcontext->disp)) { + XNextEvent (xvimagesink->xcontext->disp, &e); + + switch (e.type) { + case ClientMessage:{ + Atom wm_delete; + + wm_delete = XInternAtom (xvimagesink->xcontext->disp, + "WM_DELETE_WINDOW", True); + if (wm_delete != None && wm_delete == (Atom) e.xclient.data.l[0]) { + /* Handle window deletion by posting an error on the bus */ + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, NOT_FOUND, + ("Output window was closed"), (NULL)); + + g_mutex_unlock (xvimagesink->x_lock); + gst_xvboimagesink_xwindow_destroy (xvimagesink, xvimagesink->xwindow); + xvimagesink->xwindow = NULL; + g_mutex_lock (xvimagesink->x_lock); + } + break; + } + default: + break; + } + } + + g_mutex_unlock (xvimagesink->x_lock); + g_mutex_unlock (xvimagesink->flow_lock); +} + +static void +gst_lookup_xv_port_from_adaptor (GstXContext * xcontext, + XvAdaptorInfo * adaptors, int adaptor_no) +{ + gint j; + gint res; + + /* Do we support XvImageMask ? */ + if (!(adaptors[adaptor_no].type & XvImageMask)) { + GST_DEBUG ("XV Adaptor %s has no support for XvImageMask", + adaptors[adaptor_no].name); + return; + } + + /* We found such an adaptor, looking for an available port */ + for (j = 0; j < adaptors[adaptor_no].num_ports && !xcontext->xv_port_id; j++) { + /* We try to grab the port */ + res = XvGrabPort (xcontext->disp, adaptors[adaptor_no].base_id + j, 0); + if (Success == res) { + xcontext->xv_port_id = adaptors[adaptor_no].base_id + j; + GST_DEBUG ("XV Adaptor %s with %ld ports", adaptors[adaptor_no].name, + adaptors[adaptor_no].num_ports); + } else { + GST_DEBUG ("GrabPort %d for XV Adaptor %s failed: %d", j, + adaptors[adaptor_no].name, res); + } + } +} + +/* This function generates a caps with all supported format by the first + Xv grabable port we find. We store each one of the supported formats in a + format list and append the format to a newly created caps that we return + If this function does not return NULL because of an error, it also grabs + the port via XvGrabPort */ +static GstCaps * +gst_xvboimagesink_get_xv_support (GstXvboImageSink * xvimagesink, + GstXContext * xcontext) +{ + gint i; + XvAdaptorInfo *adaptors; + gint nb_formats; + XvImageFormatValues *formats = NULL; + guint nb_encodings; + XvEncodingInfo *encodings = NULL; + gulong max_w = G_MAXINT, max_h = G_MAXINT; + gboolean have_xvbo; + GstCaps *caps = NULL; + GstCaps *rgb_caps = NULL; + + g_return_val_if_fail (xcontext != NULL, NULL); + + /* First let's check that XVideo extension is available */ + if (!XQueryExtension (xcontext->disp, "XVideo", &i, &i, &i)) { + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, SETTINGS, + ("Could not initialise Xv output"), + ("XVideo extension is not available")); + return NULL; + } + + /* Then we get adaptors list */ + if (Success != XvQueryAdaptors (xcontext->disp, xcontext->root, + &xcontext->nb_adaptors, &adaptors)) { + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, SETTINGS, + ("Could not initialise Xv output"), + ("Failed getting XV adaptors list")); + return NULL; + } + + xcontext->xv_port_id = 0; + + GST_DEBUG ("Found %u XV adaptor(s)", xcontext->nb_adaptors); + + xcontext->adaptors = + (gchar **) g_malloc0 (xcontext->nb_adaptors * sizeof (gchar *)); + + /* Now fill up our adaptor name array */ + for (i = 0; i < xcontext->nb_adaptors; i++) { + xcontext->adaptors[i] = g_strdup (adaptors[i].name); + } + + if (xvimagesink->adaptor_no >= 0 && + xvimagesink->adaptor_no < xcontext->nb_adaptors) { + /* Find xv port from user defined adaptor */ + gst_lookup_xv_port_from_adaptor (xcontext, adaptors, + xvimagesink->adaptor_no); + } + + if (!xcontext->xv_port_id) { + /* Now search for an adaptor that supports XvImageMask */ + for (i = 0; i < xcontext->nb_adaptors && !xcontext->xv_port_id; i++) { + gst_lookup_xv_port_from_adaptor (xcontext, adaptors, i); + xvimagesink->adaptor_no = i; + } + } + + XvFreeAdaptorInfo (adaptors); + + if (!xcontext->xv_port_id) { + xvimagesink->adaptor_no = -1; + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, BUSY, + ("Could not initialise Xv output"), ("No port available")); + return NULL; + } + + /* Set XV_AUTOPAINT_COLORKEY and XV_DOUBLE_BUFFER and XV_COLORKEY */ + { + int count, todo = 3; + XvAttribute *const attr = XvQueryPortAttributes (xcontext->disp, + xcontext->xv_port_id, &count); + static const char autopaint[] = "XV_AUTOPAINT_COLORKEY"; + static const char dbl_buffer[] = "XV_DOUBLE_BUFFER"; + static const char colorkey[] = "XV_COLORKEY"; + + GST_DEBUG_OBJECT (xvimagesink, "Checking %d Xv port attributes", count); + + xvimagesink->have_autopaint_colorkey = FALSE; + xvimagesink->have_double_buffer = FALSE; + xvimagesink->have_colorkey = FALSE; + + for (i = 0; ((i < count) && todo); i++) + if (!strcmp (attr[i].name, autopaint)) { + const Atom atom = XInternAtom (xcontext->disp, autopaint, False); + + /* turn on autopaint colorkey */ + XvSetPortAttribute (xcontext->disp, xcontext->xv_port_id, atom, + (xvimagesink->autopaint_colorkey ? 1 : 0)); + todo--; + xvimagesink->have_autopaint_colorkey = TRUE; + } else if (!strcmp (attr[i].name, dbl_buffer)) { + const Atom atom = XInternAtom (xcontext->disp, dbl_buffer, False); + + XvSetPortAttribute (xcontext->disp, xcontext->xv_port_id, atom, + (xvimagesink->double_buffer ? 1 : 0)); + todo--; + xvimagesink->have_double_buffer = TRUE; + } else if (!strcmp (attr[i].name, colorkey)) { + /* Set the colorkey, default is something that is dark but hopefully + * won't randomly appear on the screen elsewhere (ie not black or greys) + * can be overridden by setting "colorkey" property + */ + const Atom atom = XInternAtom (xcontext->disp, colorkey, False); + guint32 ckey = 0; + gboolean set_attr = TRUE; + guint cr, cg, cb; + + /* set a colorkey in the right format RGB565/RGB888 + * We only handle these 2 cases, because they're the only types of + * devices we've encountered. If we don't recognise it, leave it alone + */ + cr = (xvimagesink->colorkey >> 16); + cg = (xvimagesink->colorkey >> 8) & 0xFF; + cb = (xvimagesink->colorkey) & 0xFF; + switch (xcontext->depth) { + case 16: /* RGB 565 */ + cr >>= 3; + cg >>= 2; + cb >>= 3; + ckey = (cr << 11) | (cg << 5) | cb; + break; + case 24: + case 32: /* RGB 888 / ARGB 8888 */ + ckey = (cr << 16) | (cg << 8) | cb; + break; + default: + GST_DEBUG_OBJECT (xvimagesink, + "Unknown bit depth %d for Xv Colorkey - not adjusting", + xcontext->depth); + set_attr = FALSE; + break; + } + + if (set_attr) { + ckey = CLAMP (ckey, (guint32) attr[i].min_value, + (guint32) attr[i].max_value); + GST_LOG_OBJECT (xvimagesink, + "Setting color key for display depth %d to 0x%x", + xcontext->depth, ckey); + + XvSetPortAttribute (xcontext->disp, xcontext->xv_port_id, atom, + (gint) ckey); + } + todo--; + xvimagesink->have_colorkey = TRUE; + } + + XFree (attr); + } + +#ifdef HAVE_BMMLIB + /* Get the XV Deinterlace flag, specific to Marvell's Dove XV backend */ + xcontext->xv_deinterlace = XInternAtom (xcontext->disp, "XV_DEINTERLACE", + True); + if (xcontext->xv_deinterlace != None) + XvGetPortAttribute(xcontext->disp, xcontext->xv_port_id, + xcontext->xv_deinterlace, &xcontext->xv_deinterlace_setting); +#endif + + /* Get the list of encodings supported by the adapter and look for the + * XV_IMAGE encoding so we can determine the maximum width and height + * supported */ + XvQueryEncodings (xcontext->disp, xcontext->xv_port_id, &nb_encodings, + &encodings); + + for (i = 0; i < nb_encodings; i++) { + GST_LOG_OBJECT (xvimagesink, + "Encoding %d, name %s, max wxh %lux%lu rate %d/%d", + i, encodings[i].name, encodings[i].width, encodings[i].height, + encodings[i].rate.numerator, encodings[i].rate.denominator); + if (strcmp (encodings[i].name, "XV_IMAGE") == 0) { + max_w = encodings[i].width; + max_h = encodings[i].height; + } + } + + XvFreeEncodingInfo (encodings); + + /* We get all image formats supported by our port */ + formats = XvListImageFormats (xcontext->disp, + xcontext->xv_port_id, &nb_formats); + + /* We only drive Xv with the XVBO extension; if this is not present, + fall back to another video sink (eg, the standard xvimagesink) */ + have_xvbo = FALSE; + for (i = 0; i < nb_formats; i++) { + /* Do we have the XVBO format? */ + if (formats[i].id == GST_MAKE_FOURCC ('X', 'V', 'B', 'O')) + have_xvbo = TRUE; + } + if (!have_xvbo) { + XFree (formats); + XvUngrabPort (xcontext->disp, xcontext->xv_port_id, 0); + GST_ELEMENT_ERROR (xvimagesink, STREAM, WRONG_TYPE, (NULL), + ("No XVBO support found")); + return NULL; + } + + caps = gst_caps_new_empty (); + for (i = 0; i < nb_formats; i++) { + GstCaps *format_caps = NULL; + gboolean is_rgb_format = FALSE; + + /* Ignore the XVBO format */ + if (formats[i].id == GST_MAKE_FOURCC ('X', 'V', 'B', 'O')) + continue; + + /* We set the image format of the xcontext to an existing one. This + is just some valid image format for making our xshm calls check before + caps negotiation really happens. */ + xcontext->im_format = formats[i].id; + + switch (formats[i].type) { + case XvRGB: + { + XvImageFormatValues *fmt = &(formats[i]); + gint endianness = G_BIG_ENDIAN; + + if (fmt->byte_order == LSBFirst) { + /* our caps system handles 24/32bpp RGB as big-endian. */ + if (fmt->bits_per_pixel == 24 || fmt->bits_per_pixel == 32) { + fmt->red_mask = GUINT32_TO_BE (fmt->red_mask); + fmt->green_mask = GUINT32_TO_BE (fmt->green_mask); + fmt->blue_mask = GUINT32_TO_BE (fmt->blue_mask); + + if (fmt->bits_per_pixel == 24) { + fmt->red_mask >>= 8; + fmt->green_mask >>= 8; + fmt->blue_mask >>= 8; + } + } else + endianness = G_LITTLE_ENDIAN; + } + + format_caps = gst_caps_new_simple ("video/x-raw-rgb", + "endianness", G_TYPE_INT, endianness, + "depth", G_TYPE_INT, fmt->depth, + "bpp", G_TYPE_INT, fmt->bits_per_pixel, + "red_mask", G_TYPE_INT, fmt->red_mask, + "green_mask", G_TYPE_INT, fmt->green_mask, + "blue_mask", G_TYPE_INT, fmt->blue_mask, + "width", GST_TYPE_INT_RANGE, 1, max_w, + "height", GST_TYPE_INT_RANGE, 1, max_h, + "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL); + + is_rgb_format = TRUE; + break; + } + case XvYUV: + format_caps = gst_caps_new_simple ("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, formats[i].id, + "width", GST_TYPE_INT_RANGE, 1, max_w, + "height", GST_TYPE_INT_RANGE, 1, max_h, + "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL); + break; + default: + g_assert_not_reached (); + break; + } + + if (format_caps) { + GstXvImageFormat *format = NULL; + + format = g_new0 (GstXvImageFormat, 1); + if (format) { + format->format = formats[i].id; + format->caps = gst_caps_copy (format_caps); + xcontext->formats_list = g_list_append (xcontext->formats_list, format); + } + + if (is_rgb_format) { + if (rgb_caps == NULL) + rgb_caps = format_caps; + else + gst_caps_append (rgb_caps, format_caps); + } else + gst_caps_append (caps, format_caps); + } + } + + /* Collected all caps into either the caps or rgb_caps structures. + * Append rgb_caps on the end of YUV, so that YUV is always preferred */ + if (rgb_caps) + gst_caps_append (caps, rgb_caps); + + if (formats) + XFree (formats); + + GST_DEBUG ("Generated the following caps: %" GST_PTR_FORMAT, caps); + + if (gst_caps_is_empty (caps)) { + gst_caps_unref (caps); + XvUngrabPort (xcontext->disp, xcontext->xv_port_id, 0); + GST_ELEMENT_ERROR (xvimagesink, STREAM, WRONG_TYPE, (NULL), + ("No supported format found")); + return NULL; + } + + return caps; +} + +static gpointer +gst_xvboimagesink_event_thread (GstXvboImageSink * xvimagesink) +{ + g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL); + + GST_OBJECT_LOCK (xvimagesink); + while (xvimagesink->running) { + GST_OBJECT_UNLOCK (xvimagesink); + + if (xvimagesink->xwindow) { + gst_xvboimagesink_handle_xevents (xvimagesink); + } + /* FIXME: do we want to align this with the framerate or anything else? */ + g_usleep (G_USEC_PER_SEC / 20); + + GST_OBJECT_LOCK (xvimagesink); + } + GST_OBJECT_UNLOCK (xvimagesink); + + return NULL; +} + +static void +gst_xvboimagesink_manage_event_thread (GstXvboImageSink * xvimagesink) +{ + GThread *thread = NULL; + + /* don't start the thread too early */ + if (xvimagesink->xcontext == NULL) { + return; + } + + GST_OBJECT_LOCK (xvimagesink); + if (xvimagesink->handle_expose || xvimagesink->handle_events) { + if (!xvimagesink->event_thread) { + /* Setup our event listening thread */ + GST_DEBUG_OBJECT (xvimagesink, "run xevent thread, expose %d, events %d", + xvimagesink->handle_expose, xvimagesink->handle_events); + xvimagesink->running = TRUE; + xvimagesink->event_thread = g_thread_create ( + (GThreadFunc) gst_xvboimagesink_event_thread, xvimagesink, TRUE, NULL); + } + } else { + if (xvimagesink->event_thread) { + GST_DEBUG_OBJECT (xvimagesink, "stop xevent thread, expose %d, events %d", + xvimagesink->handle_expose, xvimagesink->handle_events); + xvimagesink->running = FALSE; + /* grab thread and mark it as NULL */ + thread = xvimagesink->event_thread; + xvimagesink->event_thread = NULL; + } + } + GST_OBJECT_UNLOCK (xvimagesink); + + /* Wait for our event thread to finish */ + if (thread) + g_thread_join (thread); + +} + + +/* This function calculates the pixel aspect ratio based on the properties + * in the xcontext structure and stores it there. */ +static void +gst_xvboimagesink_calculate_pixel_aspect_ratio (GstXContext * xcontext) +{ + static const gint par[][2] = { + {1, 1}, /* regular screen */ + {16, 15}, /* PAL TV */ + {11, 10}, /* 525 line Rec.601 video */ + {54, 59}, /* 625 line Rec.601 video */ + {64, 45}, /* 1280x1024 on 16:9 display */ + {5, 3}, /* 1280x1024 on 4:3 display */ + {4, 3} /* 800x600 on 16:9 display */ + }; + gint i; + gint index; + gdouble ratio; + gdouble delta; + +#define DELTA(idx) (ABS (ratio - ((gdouble) par[idx][0] / par[idx][1]))) + + /* first calculate the "real" ratio based on the X values; + * which is the "physical" w/h divided by the w/h in pixels of the display */ + ratio = (gdouble) (xcontext->widthmm * xcontext->height) + / (xcontext->heightmm * xcontext->width); + + /* DirectFB's X in 720x576 reports the physical dimensions wrong, so + * override here */ + if (xcontext->width == 720 && xcontext->height == 576) { + ratio = 4.0 * 576 / (3.0 * 720); + } + GST_DEBUG ("calculated pixel aspect ratio: %f", ratio); + /* now find the one from par[][2] with the lowest delta to the real one */ + delta = DELTA (0); + index = 0; + + for (i = 1; i < sizeof (par) / (sizeof (gint) * 2); ++i) { + gdouble this_delta = DELTA (i); + + if (this_delta < delta) { + index = i; + delta = this_delta; + } + } + + GST_DEBUG ("Decided on index %d (%d/%d)", index, + par[index][0], par[index][1]); + + g_free (xcontext->par); + xcontext->par = g_new0 (GValue, 1); + g_value_init (xcontext->par, GST_TYPE_FRACTION); + gst_value_set_fraction (xcontext->par, par[index][0], par[index][1]); + GST_DEBUG ("set xcontext PAR to %d/%d", + gst_value_get_fraction_numerator (xcontext->par), + gst_value_get_fraction_denominator (xcontext->par)); +} + +/* This function gets the X Display and global info about it. Everything is + stored in our object and will be cleaned when the object is disposed. Note + here that caps for supported format are generated without any window or + image creation */ +static GstXContext * +gst_xvboimagesink_xcontext_get (GstXvboImageSink * xvimagesink) +{ + GstXContext *xcontext = NULL; + XPixmapFormatValues *px_formats = NULL; + gint nb_formats = 0, i, j, N_attr; + XvAttribute *xv_attr; + Atom prop_atom; + char *channels[4] = { "XV_HUE", "XV_SATURATION", + "XV_BRIGHTNESS", "XV_CONTRAST" + }; + + g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL); + + xcontext = g_new0 (GstXContext, 1); + xcontext->im_format = 0; + + g_mutex_lock (xvimagesink->x_lock); + + xcontext->disp = XOpenDisplay (xvimagesink->display_name); + + if (!xcontext->disp) { + g_mutex_unlock (xvimagesink->x_lock); + g_free (xcontext); + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE, + ("Could not initialise Xv output"), ("Could not open display")); + return NULL; + } + + xcontext->screen = DefaultScreenOfDisplay (xcontext->disp); + xcontext->screen_num = DefaultScreen (xcontext->disp); + xcontext->visual = DefaultVisual (xcontext->disp, xcontext->screen_num); + xcontext->root = DefaultRootWindow (xcontext->disp); + xcontext->white = XWhitePixel (xcontext->disp, xcontext->screen_num); + xcontext->black = XBlackPixel (xcontext->disp, xcontext->screen_num); + xcontext->depth = DefaultDepthOfScreen (xcontext->screen); + + xcontext->width = DisplayWidth (xcontext->disp, xcontext->screen_num); + xcontext->height = DisplayHeight (xcontext->disp, xcontext->screen_num); + xcontext->widthmm = DisplayWidthMM (xcontext->disp, xcontext->screen_num); + xcontext->heightmm = DisplayHeightMM (xcontext->disp, xcontext->screen_num); + + GST_DEBUG_OBJECT (xvimagesink, "X reports %dx%d pixels and %d mm x %d mm", + xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm); + + gst_xvboimagesink_calculate_pixel_aspect_ratio (xcontext); + /* We get supported pixmap formats at supported depth */ + px_formats = XListPixmapFormats (xcontext->disp, &nb_formats); + + if (!px_formats) { + XCloseDisplay (xcontext->disp); + g_mutex_unlock (xvimagesink->x_lock); + g_free (xcontext->par); + g_free (xcontext); + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, SETTINGS, + ("Could not initialise Xv output"), ("Could not get pixel formats")); + return NULL; + } + + /* We get bpp value corresponding to our running depth */ + for (i = 0; i < nb_formats; i++) { + if (px_formats[i].depth == xcontext->depth) + xcontext->bpp = px_formats[i].bits_per_pixel; + } + + XFree (px_formats); + + xcontext->endianness = + (ImageByteOrder (xcontext->disp) == + LSBFirst) ? G_LITTLE_ENDIAN : G_BIG_ENDIAN; + + /* our caps system handles 24/32bpp RGB as big-endian. */ + if ((xcontext->bpp == 24 || xcontext->bpp == 32) && + xcontext->endianness == G_LITTLE_ENDIAN) { + xcontext->endianness = G_BIG_ENDIAN; + xcontext->visual->red_mask = GUINT32_TO_BE (xcontext->visual->red_mask); + xcontext->visual->green_mask = GUINT32_TO_BE (xcontext->visual->green_mask); + xcontext->visual->blue_mask = GUINT32_TO_BE (xcontext->visual->blue_mask); + if (xcontext->bpp == 24) { + xcontext->visual->red_mask >>= 8; + xcontext->visual->green_mask >>= 8; + xcontext->visual->blue_mask >>= 8; + } + } + + xcontext->caps = gst_xvboimagesink_get_xv_support (xvimagesink, xcontext); + + if (!xcontext->caps) { + XCloseDisplay (xcontext->disp); + g_mutex_unlock (xvimagesink->x_lock); + g_free (xcontext->par); + g_free (xcontext); + /* GST_ELEMENT_ERROR is thrown by gst_xvboimagesink_get_xv_support */ + return NULL; + } +#ifdef HAVE_XSHM + /* Search for XShm extension support */ + if (XShmQueryExtension (xcontext->disp) && + gst_xvboimagesink_check_xshm_calls (xcontext)) { + xcontext->use_xshm = TRUE; + GST_DEBUG ("xvimagesink is using XShm extension"); + } else +#endif /* HAVE_XSHM */ + { + xcontext->use_xshm = FALSE; + GST_DEBUG ("xvimagesink is not using XShm extension"); + } + + xv_attr = XvQueryPortAttributes (xcontext->disp, + xcontext->xv_port_id, &N_attr); + + + /* Generate the channels list */ + for (i = 0; i < (sizeof (channels) / sizeof (char *)); i++) { + XvAttribute *matching_attr = NULL; + + /* Retrieve the property atom if it exists. If it doesn't exist, + * the attribute itself must not either, so we can skip */ + prop_atom = XInternAtom (xcontext->disp, channels[i], True); + if (prop_atom == None) + continue; + + if (xv_attr != NULL) { + for (j = 0; j < N_attr && matching_attr == NULL; ++j) + if (!g_ascii_strcasecmp (channels[i], xv_attr[j].name)) + matching_attr = xv_attr + j; + } + + if (matching_attr) { + GstColorBalanceChannel *channel; + + channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL); + channel->label = g_strdup (channels[i]); + channel->min_value = matching_attr ? matching_attr->min_value : -1000; + channel->max_value = matching_attr ? matching_attr->max_value : 1000; + + xcontext->channels_list = g_list_append (xcontext->channels_list, + channel); + + /* If the colorbalance settings have not been touched we get Xv values + as defaults and update our internal variables */ + if (!xvimagesink->cb_changed) { + gint val; + + XvGetPortAttribute (xcontext->disp, xcontext->xv_port_id, + prop_atom, &val); + /* Normalize val to [-1000, 1000] */ + val = floor (0.5 + -1000 + 2000 * (val - channel->min_value) / + (double) (channel->max_value - channel->min_value)); + + if (!g_ascii_strcasecmp (channels[i], "XV_HUE")) + xvimagesink->hue = val; + else if (!g_ascii_strcasecmp (channels[i], "XV_SATURATION")) + xvimagesink->saturation = val; + else if (!g_ascii_strcasecmp (channels[i], "XV_BRIGHTNESS")) + xvimagesink->brightness = val; + else if (!g_ascii_strcasecmp (channels[i], "XV_CONTRAST")) + xvimagesink->contrast = val; + } + } + } + + if (xv_attr) + XFree (xv_attr); + + if (xvimagesink->drm == NULL) { + xvimagesink->drm = drm_get_and_auth(xcontext->disp); + if (xvimagesink->drm == NULL) { + GList *channels_list; + + channels_list = xcontext->channels_list; + + while (channels_list) { + GstColorBalanceChannel *channel = channels_list->data; + + g_object_unref (channel); + channels_list = g_list_next (channels_list); + } + + if (xcontext->channels_list) + g_list_free (xcontext->channels_list); + + XCloseDisplay (xcontext->disp); + + g_mutex_unlock (xvimagesink->x_lock); + + g_free (xcontext->par); + g_free (xcontext); + /* GST_ELEMENT_ERROR is thrown by gst_xvboimagesink_get_xv_support */ + return NULL; + } + } + + g_mutex_unlock (xvimagesink->x_lock); + + return xcontext; +} + +/* This function cleans the X context. Closing the Display, releasing the XV + port and unrefing the caps for supported formats. */ +static void +gst_xvboimagesink_xcontext_clear (GstXvboImageSink * xvimagesink) +{ + GList *formats_list, *channels_list; + GstXContext *xcontext; + gint i = 0; + + g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink)); + + /* We're closing the display. Also close the DRM connection. */ + if (xvimagesink->drm) { + drm_close(xvimagesink->drm); + xvimagesink->drm = NULL; + } + + GST_OBJECT_LOCK (xvimagesink); + if (xvimagesink->xcontext == NULL) { + GST_OBJECT_UNLOCK (xvimagesink); + return; + } + + /* Take the XContext from the sink and clean it up */ + xcontext = xvimagesink->xcontext; + xvimagesink->xcontext = NULL; + + GST_OBJECT_UNLOCK (xvimagesink); + + + formats_list = xcontext->formats_list; + + while (formats_list) { + GstXvImageFormat *format = formats_list->data; + + gst_caps_unref (format->caps); + g_free (format); + formats_list = g_list_next (formats_list); + } + + if (xcontext->formats_list) + g_list_free (xcontext->formats_list); + + channels_list = xcontext->channels_list; + + while (channels_list) { + GstColorBalanceChannel *channel = channels_list->data; + + g_object_unref (channel); + channels_list = g_list_next (channels_list); + } + + if (xcontext->channels_list) + g_list_free (xcontext->channels_list); + + gst_caps_unref (xcontext->caps); + if (xcontext->last_caps) + gst_caps_replace (&xcontext->last_caps, NULL); + + for (i = 0; i < xcontext->nb_adaptors; i++) { + g_free (xcontext->adaptors[i]); + } + + g_free (xcontext->adaptors); + + g_free (xcontext->par); + + g_mutex_lock (xvimagesink->x_lock); + + GST_DEBUG_OBJECT (xvimagesink, "Closing display and freeing X Context"); + + XvUngrabPort (xcontext->disp, xcontext->xv_port_id, 0); + + XCloseDisplay (xcontext->disp); + + g_mutex_unlock (xvimagesink->x_lock); + + g_free (xcontext); +} + +static void +gst_xvboimagesink_imagepool_clear (GstXvboImageSink * xvimagesink) +{ + g_mutex_lock (xvimagesink->pool_lock); + + while (xvimagesink->image_pool) { + GstXvboImageBuffer *xvimage = xvimagesink->image_pool->data; + + xvimagesink->image_pool = g_slist_delete_link (xvimagesink->image_pool, + xvimagesink->image_pool); + gst_xvboimage_buffer_free (xvimage); + } + + g_mutex_unlock (xvimagesink->pool_lock); +} + +/* Element stuff */ + +/* This function tries to get a format matching with a given caps in the + supported list of formats we generated in gst_xvboimagesink_get_xv_support */ +static gint +gst_xvboimagesink_get_format_from_caps (GstXvboImageSink * xvimagesink, + GstCaps * caps) +{ + GList *list = NULL; + + g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), 0); + + list = xvimagesink->xcontext->formats_list; + + while (list) { + GstXvImageFormat *format = list->data; + + if (format) { + GstCaps *icaps = NULL; + + icaps = gst_caps_intersect (caps, format->caps); + if (!gst_caps_is_empty (icaps)) { + gst_caps_unref (icaps); + return format->format; + } + gst_caps_unref (icaps); + } + list = g_list_next (list); + } + + return -1; +} + +static GstCaps * +gst_xvboimagesink_getcaps (GstBaseSink * bsink) +{ + GstXvboImageSink *xvimagesink; + + xvimagesink = GST_XVIMAGESINK (bsink); + + if (xvimagesink->xcontext) + return gst_caps_ref (xvimagesink->xcontext->caps); + + return + gst_caps_copy (gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD + (xvimagesink))); +} + +static gboolean +gst_xvboimagesink_setcaps (GstBaseSink * bsink, GstCaps * caps) +{ + GstXvboImageSink *xvimagesink; + GstStructure *structure; + GstCaps *intersection; + guint32 im_format = 0; + gboolean ret; + gint video_width, video_height; + gint disp_x, disp_y; + gint disp_width, disp_height; + gint video_par_n, video_par_d; /* video's PAR */ + gint display_par_n, display_par_d; /* display's PAR */ + const GValue *caps_par; + const GValue *caps_disp_reg; + const GValue *fps; + guint num, den; + + xvimagesink = GST_XVIMAGESINK (bsink); + + GST_DEBUG_OBJECT (xvimagesink, + "In setcaps. Possible caps %" GST_PTR_FORMAT ", setting caps %" + GST_PTR_FORMAT, xvimagesink->xcontext->caps, caps); + + intersection = gst_caps_intersect (xvimagesink->xcontext->caps, caps); + GST_DEBUG_OBJECT (xvimagesink, "intersection returned %" GST_PTR_FORMAT, + intersection); + if (gst_caps_is_empty (intersection)) + goto incompatible_caps; + + gst_caps_unref (intersection); + + structure = gst_caps_get_structure (caps, 0); + ret = gst_structure_get_int (structure, "width", &video_width); + ret &= gst_structure_get_int (structure, "height", &video_height); + fps = gst_structure_get_value (structure, "framerate"); + ret &= (fps != NULL); + + if (!ret) + goto incomplete_caps; + + xvimagesink->fps_n = gst_value_get_fraction_numerator (fps); + xvimagesink->fps_d = gst_value_get_fraction_denominator (fps); + + xvimagesink->video_width = video_width; + xvimagesink->video_height = video_height; + + im_format = gst_xvboimagesink_get_format_from_caps (xvimagesink, caps); + if (im_format == -1) + goto invalid_format; + + /* get aspect ratio from caps if it's present, and + * convert video width and height to a display width and height + * using wd / hd = wv / hv * PARv / PARd */ + + /* get video's PAR */ + caps_par = gst_structure_get_value (structure, "pixel-aspect-ratio"); + if (caps_par) { + video_par_n = gst_value_get_fraction_numerator (caps_par); + video_par_d = gst_value_get_fraction_denominator (caps_par); + } else { + video_par_n = 1; + video_par_d = 1; + } + /* get display's PAR */ + if (xvimagesink->par) { + display_par_n = gst_value_get_fraction_numerator (xvimagesink->par); + display_par_d = gst_value_get_fraction_denominator (xvimagesink->par); + } else { + display_par_n = 1; + display_par_d = 1; + } + + /* get the display region */ + caps_disp_reg = gst_structure_get_value (structure, "display-region"); + if (caps_disp_reg) { + disp_x = g_value_get_int (gst_value_array_get_value (caps_disp_reg, 0)); + disp_y = g_value_get_int (gst_value_array_get_value (caps_disp_reg, 1)); + disp_width = g_value_get_int (gst_value_array_get_value (caps_disp_reg, 2)); + disp_height = + g_value_get_int (gst_value_array_get_value (caps_disp_reg, 3)); + } else { + disp_x = disp_y = 0; + disp_width = video_width; + disp_height = video_height; + } + + if (!gst_video_calculate_display_ratio (&num, &den, video_width, + video_height, video_par_n, video_par_d, display_par_n, display_par_d)) + goto no_disp_ratio; + + xvimagesink->disp_x = disp_x; + xvimagesink->disp_y = disp_y; + xvimagesink->disp_width = disp_width; + xvimagesink->disp_height = disp_height; + + GST_DEBUG_OBJECT (xvimagesink, + "video width/height: %dx%d, calculated display ratio: %d/%d", + video_width, video_height, num, den); + + /* now find a width x height that respects this display ratio. + * prefer those that have one of w/h the same as the incoming video + * using wd / hd = num / den */ + + /* start with same height, because of interlaced video */ + /* check hd / den is an integer scale factor, and scale wd with the PAR */ + if (video_height % den == 0) { + GST_DEBUG_OBJECT (xvimagesink, "keeping video height"); + GST_VIDEO_SINK_WIDTH (xvimagesink) = (guint) + gst_util_uint64_scale_int (video_height, num, den); + GST_VIDEO_SINK_HEIGHT (xvimagesink) = video_height; + } else if (video_width % num == 0) { + GST_DEBUG_OBJECT (xvimagesink, "keeping video width"); + GST_VIDEO_SINK_WIDTH (xvimagesink) = video_width; + GST_VIDEO_SINK_HEIGHT (xvimagesink) = (guint) + gst_util_uint64_scale_int (video_width, den, num); + } else { + GST_DEBUG_OBJECT (xvimagesink, "approximating while keeping video height"); + GST_VIDEO_SINK_WIDTH (xvimagesink) = (guint) + gst_util_uint64_scale_int (video_height, num, den); + GST_VIDEO_SINK_HEIGHT (xvimagesink) = video_height; + } + GST_DEBUG_OBJECT (xvimagesink, "scaling to %dx%d", + GST_VIDEO_SINK_WIDTH (xvimagesink), GST_VIDEO_SINK_HEIGHT (xvimagesink)); + + /* Notify application to set xwindow id now */ + g_mutex_lock (xvimagesink->flow_lock); + if (!xvimagesink->xwindow) { + g_mutex_unlock (xvimagesink->flow_lock); + gst_x_overlay_prepare_xwindow_id (GST_X_OVERLAY (xvimagesink)); + } else { + g_mutex_unlock (xvimagesink->flow_lock); + } + + /* Creating our window and our image with the display size in pixels */ + if (GST_VIDEO_SINK_WIDTH (xvimagesink) <= 0 || + GST_VIDEO_SINK_HEIGHT (xvimagesink) <= 0) + goto no_display_size; + + g_mutex_lock (xvimagesink->flow_lock); + if (!xvimagesink->xwindow) { + xvimagesink->xwindow = gst_xvboimagesink_xwindow_new (xvimagesink, + GST_VIDEO_SINK_WIDTH (xvimagesink), + GST_VIDEO_SINK_HEIGHT (xvimagesink)); + } + + /* After a resize, we want to redraw the borders in case the new frame size + * doesn't cover the same area */ + xvimagesink->redraw_border = TRUE; + + /* We renew our xvimage only if size or format changed; + * the xvimage is the same size as the video pixel size */ + if ((xvimagesink->xvimage) && + ((im_format != xvimagesink->xvimage->im_format) || + (video_width != xvimagesink->xvimage->width) || + (video_height != xvimagesink->xvimage->height))) { + GST_DEBUG_OBJECT (xvimagesink, + "old format %" GST_FOURCC_FORMAT ", new format %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (xvimagesink->xvimage->im_format), + GST_FOURCC_ARGS (im_format)); + GST_DEBUG_OBJECT (xvimagesink, "renewing xvimage"); + gst_buffer_unref (GST_BUFFER (xvimagesink->xvimage)); + xvimagesink->xvimage = NULL; + } + + g_mutex_unlock (xvimagesink->flow_lock); + + return TRUE; + + /* ERRORS */ +incompatible_caps: + { + GST_ERROR_OBJECT (xvimagesink, "caps incompatible"); + gst_caps_unref (intersection); + return FALSE; + } +incomplete_caps: + { + GST_DEBUG_OBJECT (xvimagesink, "Failed to retrieve either width, " + "height or framerate from intersected caps"); + return FALSE; + } +invalid_format: + { + GST_DEBUG_OBJECT (xvimagesink, + "Could not locate image format from caps %" GST_PTR_FORMAT, caps); + return FALSE; + } +no_disp_ratio: + { + GST_ELEMENT_ERROR (xvimagesink, CORE, NEGOTIATION, (NULL), + ("Error calculating the output display ratio of the video.")); + return FALSE; + } +no_display_size: + { + GST_ELEMENT_ERROR (xvimagesink, CORE, NEGOTIATION, (NULL), + ("Error calculating the output display ratio of the video.")); + return FALSE; + } +} + +static GstStateChangeReturn +gst_xvboimagesink_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstXvboImageSink *xvimagesink; + GstXContext *xcontext = NULL; + + xvimagesink = GST_XVIMAGESINK (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + /* Initializing the XContext */ + if (xvimagesink->xcontext == NULL) { + xcontext = gst_xvboimagesink_xcontext_get (xvimagesink); + if (xcontext == NULL) + return GST_STATE_CHANGE_FAILURE; + GST_OBJECT_LOCK (xvimagesink); + if (xcontext) + xvimagesink->xcontext = xcontext; + GST_OBJECT_UNLOCK (xvimagesink); + } + + /* update object's par with calculated one if not set yet */ + if (!xvimagesink->par) { + xvimagesink->par = g_new0 (GValue, 1); + gst_value_init_and_copy (xvimagesink->par, xvimagesink->xcontext->par); + GST_DEBUG_OBJECT (xvimagesink, "set calculated PAR on object's PAR"); + } + /* call XSynchronize with the current value of synchronous */ + GST_DEBUG_OBJECT (xvimagesink, "XSynchronize called with %s", + xvimagesink->synchronous ? "TRUE" : "FALSE"); + XSynchronize (xvimagesink->xcontext->disp, xvimagesink->synchronous); + gst_xvboimagesink_update_colorbalance (xvimagesink); + gst_xvboimagesink_manage_event_thread (xvimagesink); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + g_mutex_lock (xvimagesink->pool_lock); + xvimagesink->pool_invalid = FALSE; + g_mutex_unlock (xvimagesink->pool_lock); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + g_mutex_lock (xvimagesink->pool_lock); + xvimagesink->pool_invalid = TRUE; + g_mutex_unlock (xvimagesink->pool_lock); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + xvimagesink->fps_n = 0; + xvimagesink->fps_d = 1; + GST_VIDEO_SINK_WIDTH (xvimagesink) = 0; + GST_VIDEO_SINK_HEIGHT (xvimagesink) = 0; + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_xvboimagesink_reset (xvimagesink); + break; + default: + break; + } + + return ret; +} + +static void +gst_xvboimagesink_get_times (GstBaseSink * bsink, GstBuffer * buf, + GstClockTime * start, GstClockTime * end) +{ + GstXvboImageSink *xvimagesink; + + xvimagesink = GST_XVIMAGESINK (bsink); + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) { + *start = GST_BUFFER_TIMESTAMP (buf); + if (GST_BUFFER_DURATION_IS_VALID (buf)) { + *end = *start + GST_BUFFER_DURATION (buf); + } else { + if (xvimagesink->fps_n > 0) { + *end = *start + + gst_util_uint64_scale_int (GST_SECOND, xvimagesink->fps_d, + xvimagesink->fps_n); + } + } + } +} + +static GstFlowReturn +gst_xvboimagesink_show_frame (GstVideoSink * vsink, GstBuffer * buf) +{ + GstXvboImageSink *xvimagesink; + + xvimagesink = GST_XVIMAGESINK (vsink); + + /* If this buffer has been allocated using our buffer management we simply + put the ximage which is in the PRIVATE pointer */ + if (GST_IS_XVIMAGE_BUFFER (buf)) { + GST_LOG_OBJECT (xvimagesink, "fast put of bufferpool buffer %p", buf); + if (!gst_xvboimagesink_xvimage_put (xvimagesink, + GST_XVIMAGE_BUFFER_CAST (buf))) + goto no_window; + } else { + GstXvboImageBuffer *xvbuf; + int is_bmm; + + GST_CAT_LOG_OBJECT (GST_CAT_PERFORMANCE, xvimagesink, + "slow copy into bufferpool buffer %p", buf); + /* Else we have to copy the data into our private image, */ + /* if we have one... */ + if (!xvimagesink->xvimage) { + GST_DEBUG_OBJECT (xvimagesink, "creating our xvimage"); + + xvimagesink->xvimage = gst_xvboimagesink_xvimage_new (xvimagesink, + GST_BUFFER_CAPS (buf)); + + if (!xvimagesink->xvimage) + /* The create method should have posted an informative error */ + goto no_image; + + if (xvimagesink->xvimage->size < GST_BUFFER_SIZE (buf)) { + GST_ELEMENT_ERROR (xvimagesink, RESOURCE, WRITE, + ("Failed to create output image buffer of %dx%d pixels", + xvimagesink->xvimage->width, xvimagesink->xvimage->height), + ("XServer allocated buffer size did not match input buffer")); + + gst_xvboimage_buffer_destroy (xvimagesink->xvimage); + xvimagesink->xvimage = NULL; + goto no_image; + } + } + + xvbuf = xvimagesink->xvimage; + + is_bmm = 0; + + if (xvbuf->xvbo_xvimage) { + int fd = bmm_dmabuf_fd(GST_BUFFER_DATA(buf)); + /* bmm_dmabuf keeps the fd around while the buffer exists (it's the + buffer's identifier) so we can use this as a unique way to id + the same buffer. */ + if (fd != xvbuf->bmm_dmabuf_fd) { + gst_xvboimagesink_import_cleanup(xvimagesink, xvbuf); + + /* Shuffle the paddr down, and free the last one. We need to hold + a reference on the GST buffer for the currently displayed buffer, + as well as the next buffer and the new buffer. */ + if (xvimagesink->drm_ring[xvimagesink->drm_ring_idx]) + gst_buffer_unref(xvimagesink->drm_ring[xvimagesink->drm_ring_idx]); + xvimagesink->drm_ring[xvimagesink->drm_ring_idx] = xvbuf->xvbo_buffer; + if (++xvimagesink->drm_ring_idx >= NR_DRM_RING) + xvimagesink->drm_ring_idx = 0; + + xvbuf->xvbo_buffer = NULL; + xvbuf->bmm_dmabuf_fd = -1; + xvbuf->is_xvbo = FALSE; + + if (fd != -1) + /* If we have a bmm buffer, we can convert that to a DRM buffer + object here and save the X server from having to do that. We + then pass its FOURCC and a global BO name to the X server. + This is a much better solution than the BMM shm thing. */ + gst_xvboimagesink_import(xvimagesink, xvbuf, buf, fd); + } + if (xvbuf->is_xvbo) + is_bmm = 1; + } + + /* Check IPP deinterlace flag, but only if we have the Atom */ + if (xvimagesink->xcontext->xv_deinterlace != None) { + int deinterlace = is_bmm && IPPGST_BUFFER_CUSTOMDATA(buf) ? 1 : 0; + + g_mutex_lock (xvimagesink->x_lock); + if (deinterlace != xvimagesink->xcontext->xv_deinterlace_setting) { + GST_LOG_OBJECT(xvimagesink, + "Detected new xv_deinterlace attributes: %d\n", deinterlace); + XvSetPortAttribute (xvimagesink->xcontext->disp, + xvimagesink->xcontext->xv_port_id, + xvimagesink->xcontext->xv_deinterlace, deinterlace); + + xvimagesink->xcontext->xv_deinterlace_setting = deinterlace; + } + g_mutex_unlock (xvimagesink->x_lock); + } + + if (!is_bmm) { + memcpy (xvbuf->xvimage->data, + GST_BUFFER_DATA (buf), + MIN (GST_BUFFER_SIZE (buf), xvbuf->size)); + } + + if (!gst_xvboimagesink_xvimage_put (xvimagesink, xvbuf)) + goto no_window; + } + + return GST_FLOW_OK; + + /* ERRORS */ +no_image: + { + /* No image available. That's very bad ! */ + GST_WARNING_OBJECT (xvimagesink, "could not create image"); + return GST_FLOW_ERROR; + } +no_window: + { + /* No Window available to put our image into */ + GST_WARNING_OBJECT (xvimagesink, "could not output image - no window"); + return GST_FLOW_ERROR; + } +} + +static gboolean +gst_xvboimagesink_event (GstBaseSink * sink, GstEvent * event) +{ + GstXvboImageSink *xvimagesink = GST_XVIMAGESINK (sink); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_TAG:{ + GstTagList *l; + gchar *title = NULL; + + gst_event_parse_tag (event, &l); + gst_tag_list_get_string (l, GST_TAG_TITLE, &title); + + if (title) { + GST_DEBUG_OBJECT (xvimagesink, "got tags, title='%s'", title); + gst_xvboimagesink_xwindow_set_title (xvimagesink, xvimagesink->xwindow, + title); + + g_free (title); + } + break; + } + default: + break; + } + if (GST_BASE_SINK_CLASS (parent_class)->event) + return GST_BASE_SINK_CLASS (parent_class)->event (sink, event); + else + return TRUE; +} + +/* Buffer management */ + +static GstFlowReturn +gst_xvboimagesink_buffer_alloc (GstBaseSink * bsink, guint64 offset, guint size, + GstCaps * caps, GstBuffer ** buf) +{ + GstFlowReturn ret = GST_FLOW_OK; + GstXvboImageSink *xvimagesink; + GstXvboImageBuffer *xvimage = NULL; + GstCaps *intersection = NULL; + GstStructure *structure = NULL; + gint width, height, image_format; + GstCaps *new_caps; + + xvimagesink = GST_XVIMAGESINK (bsink); + + g_mutex_lock (xvimagesink->pool_lock); + if (G_UNLIKELY (xvimagesink->pool_invalid)) + goto invalid; + + if (G_LIKELY (xvimagesink->xcontext->last_caps && + gst_caps_is_equal (caps, xvimagesink->xcontext->last_caps))) { + GST_LOG_OBJECT (xvimagesink, + "buffer alloc for same last_caps, reusing caps"); + intersection = gst_caps_ref (caps); + image_format = xvimagesink->xcontext->last_format; + width = xvimagesink->xcontext->last_width; + height = xvimagesink->xcontext->last_height; + + goto reuse_last_caps; + } + + GST_DEBUG_OBJECT (xvimagesink, "buffer alloc requested size %d with caps %" + GST_PTR_FORMAT ", intersecting with our caps %" GST_PTR_FORMAT, size, + caps, xvimagesink->xcontext->caps); + + /* Check the caps against our xcontext */ + intersection = gst_caps_intersect (xvimagesink->xcontext->caps, caps); + + /* Ensure the returned caps are fixed */ + gst_caps_truncate (intersection); + + GST_DEBUG_OBJECT (xvimagesink, "intersection in buffer alloc returned %" + GST_PTR_FORMAT, intersection); + + if (gst_caps_is_empty (intersection)) { + /* So we don't support this kind of buffer, let's define one we'd like */ + new_caps = gst_caps_copy (caps); + + structure = gst_caps_get_structure (new_caps, 0); + + /* Try with YUV first */ + gst_structure_set_name (structure, "video/x-raw-yuv"); + gst_structure_remove_field (structure, "format"); + gst_structure_remove_field (structure, "endianness"); + gst_structure_remove_field (structure, "depth"); + gst_structure_remove_field (structure, "bpp"); + gst_structure_remove_field (structure, "red_mask"); + gst_structure_remove_field (structure, "green_mask"); + gst_structure_remove_field (structure, "blue_mask"); + gst_structure_remove_field (structure, "alpha_mask"); + + /* Reuse intersection with Xcontext */ + gst_caps_unref (intersection); + intersection = gst_caps_intersect (xvimagesink->xcontext->caps, new_caps); + + if (gst_caps_is_empty (intersection)) { + /* Now try with RGB */ + gst_structure_set_name (structure, "video/x-raw-rgb"); + /* And interset again */ + gst_caps_unref (intersection); + intersection = gst_caps_intersect (xvimagesink->xcontext->caps, new_caps); + + if (gst_caps_is_empty (intersection)) + goto incompatible; + } + + /* Clean this copy */ + gst_caps_unref (new_caps); + /* We want fixed caps */ + gst_caps_truncate (intersection); + + GST_DEBUG_OBJECT (xvimagesink, "allocating a buffer with caps %" + GST_PTR_FORMAT, intersection); + } else if (gst_caps_is_equal (intersection, caps)) { + /* Things work better if we return a buffer with the same caps ptr + * as was asked for when we can */ + gst_caps_replace (&intersection, caps); + } + + /* Get image format from caps */ + image_format = gst_xvboimagesink_get_format_from_caps (xvimagesink, + intersection); + + /* Get geometry from caps */ + structure = gst_caps_get_structure (intersection, 0); + if (!gst_structure_get_int (structure, "width", &width) || + !gst_structure_get_int (structure, "height", &height) || + image_format == -1) + goto invalid_caps; + + /* Store our caps and format as the last_caps to avoid expensive + * caps intersection next time */ + gst_caps_replace (&xvimagesink->xcontext->last_caps, intersection); + xvimagesink->xcontext->last_format = image_format; + xvimagesink->xcontext->last_width = width; + xvimagesink->xcontext->last_height = height; + +reuse_last_caps: + + /* Walking through the pool cleaning unusable images and searching for a + suitable one */ + while (xvimagesink->image_pool) { + xvimage = xvimagesink->image_pool->data; + + if (xvimage) { + /* Removing from the pool */ + xvimagesink->image_pool = g_slist_delete_link (xvimagesink->image_pool, + xvimagesink->image_pool); + + /* We check for geometry or image format changes */ + if ((xvimage->width != width) || + (xvimage->height != height) || (xvimage->im_format != image_format)) { + /* This image is unusable. Destroying... */ + gst_xvboimage_buffer_free (xvimage); + xvimage = NULL; + } else { + /* We found a suitable image */ + GST_LOG_OBJECT (xvimagesink, "found usable image in pool"); + break; + } + } + } + + if (!xvimage) { + /* We found no suitable image in the pool. Creating... */ + GST_DEBUG_OBJECT (xvimagesink, "no usable image in pool, creating xvimage"); + xvimage = gst_xvboimagesink_xvimage_new (xvimagesink, intersection); + if (xvimage && xvimage->size < size) { + /* This image is unusable. Destroying... */ + GST_LOG_OBJECT (xvimagesink, "Discarding allocated buffer as unsuitable. " + "Falling back to normal buffer"); + gst_xvboimage_buffer_free (xvimage); + xvimage = NULL; + } + } + g_mutex_unlock (xvimagesink->pool_lock); + + if (xvimage) { + /* Make sure the buffer is cleared of any previously used flags */ + GST_MINI_OBJECT_CAST (xvimage)->flags = 0; + gst_buffer_set_caps (GST_BUFFER_CAST (xvimage), intersection); + } + + *buf = GST_BUFFER_CAST (xvimage); + +beach: + if (intersection) { + gst_caps_unref (intersection); + } + + return ret; + + /* ERRORS */ +invalid: + { + GST_DEBUG_OBJECT (xvimagesink, "the pool is flushing"); + ret = GST_FLOW_WRONG_STATE; + g_mutex_unlock (xvimagesink->pool_lock); + goto beach; + } +incompatible: + { + GST_WARNING_OBJECT (xvimagesink, "we were requested a buffer with " + "caps %" GST_PTR_FORMAT ", but our xcontext caps %" GST_PTR_FORMAT + " are completely incompatible with those caps", new_caps, + xvimagesink->xcontext->caps); + gst_caps_unref (new_caps); + ret = GST_FLOW_NOT_NEGOTIATED; + g_mutex_unlock (xvimagesink->pool_lock); + goto beach; + } +invalid_caps: + { + GST_WARNING_OBJECT (xvimagesink, "invalid caps for buffer allocation %" + GST_PTR_FORMAT, intersection); + ret = GST_FLOW_NOT_NEGOTIATED; + g_mutex_unlock (xvimagesink->pool_lock); + goto beach; + } +} + +/* Interfaces stuff */ + +static gboolean +gst_xvboimagesink_interface_supported (GstImplementsInterface * iface, GType type) +{ + g_assert (type == GST_TYPE_NAVIGATION || type == GST_TYPE_X_OVERLAY || + type == GST_TYPE_COLOR_BALANCE || type == GST_TYPE_PROPERTY_PROBE); + return TRUE; +} + +static void +gst_xvboimagesink_interface_init (GstImplementsInterfaceClass * klass) +{ + klass->supported = gst_xvboimagesink_interface_supported; +} + +static void +gst_xvboimagesink_navigation_send_event (GstNavigation * navigation, + GstStructure * structure) +{ + GstXvboImageSink *xvimagesink = GST_XVIMAGESINK (navigation); + GstPad *peer; + + if ((peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (xvimagesink)))) { + GstEvent *event; + GstVideoRectangle src, dst, result; + gdouble x, y, xscale = 1.0, yscale = 1.0; + + event = gst_event_new_navigation (structure); + + /* We take the flow_lock while we look at the window */ + g_mutex_lock (xvimagesink->flow_lock); + + if (!xvimagesink->xwindow) { + g_mutex_unlock (xvimagesink->flow_lock); + return; + } + + /* We get the frame position using the calculated geometry from _setcaps + that respect pixel aspect ratios */ + src.w = GST_VIDEO_SINK_WIDTH (xvimagesink); + src.h = GST_VIDEO_SINK_HEIGHT (xvimagesink); + dst.w = xvimagesink->xwindow->width; + dst.h = xvimagesink->xwindow->height; + + g_mutex_unlock (xvimagesink->flow_lock); + + if (xvimagesink->keep_aspect) { + gst_video_sink_center_rect (src, dst, &result, TRUE); + } else { + result.x = result.y = 0; + result.w = dst.w; + result.h = dst.h; + } + + /* We calculate scaling using the original video frames geometry to include + pixel aspect ratio scaling. */ + xscale = (gdouble) xvimagesink->video_width / result.w; + yscale = (gdouble) xvimagesink->video_height / result.h; + + /* Converting pointer coordinates to the non scaled geometry */ + if (gst_structure_get_double (structure, "pointer_x", &x)) { + x = MIN (x, result.x + result.w); + x = MAX (x - result.x, 0); + gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE, + (gdouble) x * xscale, NULL); + } + if (gst_structure_get_double (structure, "pointer_y", &y)) { + y = MIN (y, result.y + result.h); + y = MAX (y - result.y, 0); + gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE, + (gdouble) y * yscale, NULL); + } + + gst_pad_send_event (peer, event); + gst_object_unref (peer); + } +} + +static void +gst_xvboimagesink_navigation_init (GstNavigationInterface * iface) +{ + iface->send_event = gst_xvboimagesink_navigation_send_event; +} + +static void +gst_xvboimagesink_set_xwindow_id (GstXOverlay * overlay, XID xwindow_id) +{ + GstXvboImageSink *xvimagesink = GST_XVIMAGESINK (overlay); + GstXWindow *xwindow = NULL; + XWindowAttributes attr; + + g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink)); + + g_mutex_lock (xvimagesink->flow_lock); + + /* If we already use that window return */ + if (xvimagesink->xwindow && (xwindow_id == xvimagesink->xwindow->win)) { + g_mutex_unlock (xvimagesink->flow_lock); + return; + } + + /* If the element has not initialized the X11 context try to do so */ + if (!xvimagesink->xcontext && + !(xvimagesink->xcontext = gst_xvboimagesink_xcontext_get (xvimagesink))) { + g_mutex_unlock (xvimagesink->flow_lock); + /* we have thrown a GST_ELEMENT_ERROR now */ + return; + } + + gst_xvboimagesink_update_colorbalance (xvimagesink); + + /* Clear image pool as the images are unusable anyway */ + gst_xvboimagesink_imagepool_clear (xvimagesink); + + /* Clear the xvimage */ + if (xvimagesink->xvimage) { + gst_xvboimage_buffer_free (xvimagesink->xvimage); + xvimagesink->xvimage = NULL; + } + + /* If a window is there already we destroy it */ + if (xvimagesink->xwindow) { + gst_xvboimagesink_xwindow_destroy (xvimagesink, xvimagesink->xwindow); + xvimagesink->xwindow = NULL; + } + + /* If the xid is 0 we go back to an internal window */ + if (xwindow_id == 0) { + /* If no width/height caps nego did not happen window will be created + during caps nego then */ + if (GST_VIDEO_SINK_WIDTH (xvimagesink) + && GST_VIDEO_SINK_HEIGHT (xvimagesink)) { + xwindow = + gst_xvboimagesink_xwindow_new (xvimagesink, + GST_VIDEO_SINK_WIDTH (xvimagesink), + GST_VIDEO_SINK_HEIGHT (xvimagesink)); + } + } else { + xwindow = g_new0 (GstXWindow, 1); + + xwindow->win = xwindow_id; + + /* We get window geometry, set the event we want to receive, + and create a GC */ + g_mutex_lock (xvimagesink->x_lock); + XGetWindowAttributes (xvimagesink->xcontext->disp, xwindow->win, &attr); + xwindow->width = attr.width; + xwindow->height = attr.height; + xwindow->internal = FALSE; + if (xvimagesink->handle_events) { + XSelectInput (xvimagesink->xcontext->disp, xwindow->win, ExposureMask | + StructureNotifyMask | PointerMotionMask | KeyPressMask | + KeyReleaseMask | VisibilityChangeMask); + } + + xwindow->gc = XCreateGC (xvimagesink->xcontext->disp, + xwindow->win, 0, NULL); + g_mutex_unlock (xvimagesink->x_lock); + } + + if (xwindow) + xvimagesink->xwindow = xwindow; + + g_mutex_unlock (xvimagesink->flow_lock); +} + +static void +gst_xvboimagesink_expose (GstXOverlay * overlay) +{ + GstXvboImageSink *xvimagesink = GST_XVIMAGESINK (overlay); + + gst_xvboimagesink_xvimage_put (xvimagesink, NULL); +} + +static void +gst_xvboimagesink_set_event_handling (GstXOverlay * overlay, + gboolean handle_events) +{ + GstXvboImageSink *xvimagesink = GST_XVIMAGESINK (overlay); + + xvimagesink->handle_events = handle_events; + + g_mutex_lock (xvimagesink->flow_lock); + + if (G_UNLIKELY (!xvimagesink->xwindow)) { + g_mutex_unlock (xvimagesink->flow_lock); + return; + } + + g_mutex_lock (xvimagesink->x_lock); + + if (handle_events) { + if (xvimagesink->xwindow->internal) { + XSelectInput (xvimagesink->xcontext->disp, xvimagesink->xwindow->win, + ExposureMask | StructureNotifyMask | PointerMotionMask | + KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | VisibilityChangeMask); + } else { + XSelectInput (xvimagesink->xcontext->disp, xvimagesink->xwindow->win, + ExposureMask | StructureNotifyMask | PointerMotionMask | + KeyPressMask | KeyReleaseMask | VisibilityChangeMask); + } + } else { + XSelectInput (xvimagesink->xcontext->disp, xvimagesink->xwindow->win, 0); + } + + g_mutex_unlock (xvimagesink->x_lock); + + g_mutex_unlock (xvimagesink->flow_lock); +} + +static void +gst_xvboimagesink_xoverlay_init (GstXOverlayClass * iface) +{ + iface->set_xwindow_id = gst_xvboimagesink_set_xwindow_id; + iface->expose = gst_xvboimagesink_expose; + iface->handle_events = gst_xvboimagesink_set_event_handling; +} + +static const GList * +gst_xvboimagesink_colorbalance_list_channels (GstColorBalance * balance) +{ + GstXvboImageSink *xvimagesink = GST_XVIMAGESINK (balance); + + g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), NULL); + + if (xvimagesink->xcontext) + return xvimagesink->xcontext->channels_list; + else + return NULL; +} + +static void +gst_xvboimagesink_colorbalance_set_value (GstColorBalance * balance, + GstColorBalanceChannel * channel, gint value) +{ + GstXvboImageSink *xvimagesink = GST_XVIMAGESINK (balance); + + g_return_if_fail (GST_IS_XVIMAGESINK (xvimagesink)); + g_return_if_fail (channel->label != NULL); + + xvimagesink->cb_changed = TRUE; + + /* Normalize val to [-1000, 1000] */ + value = floor (0.5 + -1000 + 2000 * (value - channel->min_value) / + (double) (channel->max_value - channel->min_value)); + + if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) { + xvimagesink->hue = value; + } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) { + xvimagesink->saturation = value; + } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) { + xvimagesink->contrast = value; + } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) { + xvimagesink->brightness = value; + } else { + g_warning ("got an unknown channel %s", channel->label); + return; + } + + gst_xvboimagesink_update_colorbalance (xvimagesink); +} + +static gint +gst_xvboimagesink_colorbalance_get_value (GstColorBalance * balance, + GstColorBalanceChannel * channel) +{ + GstXvboImageSink *xvimagesink = GST_XVIMAGESINK (balance); + gint value = 0; + + g_return_val_if_fail (GST_IS_XVIMAGESINK (xvimagesink), 0); + g_return_val_if_fail (channel->label != NULL, 0); + + if (g_ascii_strcasecmp (channel->label, "XV_HUE") == 0) { + value = xvimagesink->hue; + } else if (g_ascii_strcasecmp (channel->label, "XV_SATURATION") == 0) { + value = xvimagesink->saturation; + } else if (g_ascii_strcasecmp (channel->label, "XV_CONTRAST") == 0) { + value = xvimagesink->contrast; + } else if (g_ascii_strcasecmp (channel->label, "XV_BRIGHTNESS") == 0) { + value = xvimagesink->brightness; + } else { + g_warning ("got an unknown channel %s", channel->label); + } + + /* Normalize val to [channel->min_value, channel->max_value] */ + value = channel->min_value + (channel->max_value - channel->min_value) * + (value + 1000) / 2000; + + return value; +} + +static void +gst_xvboimagesink_colorbalance_init (GstColorBalanceClass * iface) +{ + GST_COLOR_BALANCE_TYPE (iface) = GST_COLOR_BALANCE_HARDWARE; + iface->list_channels = gst_xvboimagesink_colorbalance_list_channels; + iface->set_value = gst_xvboimagesink_colorbalance_set_value; + iface->get_value = gst_xvboimagesink_colorbalance_get_value; +} + +static const GList * +gst_xvboimagesink_probe_get_properties (GstPropertyProbe * probe) +{ + GObjectClass *klass = G_OBJECT_GET_CLASS (probe); + static GList *list = NULL; + + if (!list) { + list = g_list_append (NULL, g_object_class_find_property (klass, "device")); + list = + g_list_append (list, g_object_class_find_property (klass, + "autopaint-colorkey")); + list = + g_list_append (list, g_object_class_find_property (klass, + "double-buffer")); + list = + g_list_append (list, g_object_class_find_property (klass, "colorkey")); + } + + return list; +} + +static void +gst_xvboimagesink_probe_probe_property (GstPropertyProbe * probe, + guint prop_id, const GParamSpec * pspec) +{ + GstXvboImageSink *xvimagesink = GST_XVIMAGESINK (probe); + + switch (prop_id) { + case ARG_DEVICE: + case ARG_AUTOPAINT_COLORKEY: + case ARG_DOUBLE_BUFFER: + case ARG_COLORKEY: + GST_DEBUG_OBJECT (xvimagesink, + "probing device list and get capabilities"); + if (!xvimagesink->xcontext) { + GST_DEBUG_OBJECT (xvimagesink, "generating xcontext"); + xvimagesink->xcontext = gst_xvboimagesink_xcontext_get (xvimagesink); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); + break; + } +} + +static gboolean +gst_xvboimagesink_probe_needs_probe (GstPropertyProbe * probe, + guint prop_id, const GParamSpec * pspec) +{ + GstXvboImageSink *xvimagesink = GST_XVIMAGESINK (probe); + gboolean ret = FALSE; + + switch (prop_id) { + case ARG_DEVICE: + case ARG_AUTOPAINT_COLORKEY: + case ARG_DOUBLE_BUFFER: + case ARG_COLORKEY: + if (xvimagesink->xcontext != NULL) { + ret = FALSE; + } else { + ret = TRUE; + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); + break; + } + + return ret; +} + +static GValueArray * +gst_xvboimagesink_probe_get_values (GstPropertyProbe * probe, + guint prop_id, const GParamSpec * pspec) +{ + GstXvboImageSink *xvimagesink = GST_XVIMAGESINK (probe); + GValueArray *array = NULL; + + if (G_UNLIKELY (!xvimagesink->xcontext)) { + GST_WARNING_OBJECT (xvimagesink, "we don't have any xcontext, can't " + "get values"); + goto beach; + } + + switch (prop_id) { + case ARG_DEVICE: + { + guint i; + GValue value = { 0 }; + + array = g_value_array_new (xvimagesink->xcontext->nb_adaptors); + g_value_init (&value, G_TYPE_STRING); + + for (i = 0; i < xvimagesink->xcontext->nb_adaptors; i++) { + gchar *adaptor_id_s = g_strdup_printf ("%u", i); + + g_value_set_string (&value, adaptor_id_s); + g_value_array_append (array, &value); + g_free (adaptor_id_s); + } + g_value_unset (&value); + break; + } + case ARG_AUTOPAINT_COLORKEY: + if (xvimagesink->have_autopaint_colorkey) { + GValue value = { 0 }; + + array = g_value_array_new (2); + g_value_init (&value, G_TYPE_BOOLEAN); + g_value_set_boolean (&value, FALSE); + g_value_array_append (array, &value); + g_value_set_boolean (&value, TRUE); + g_value_array_append (array, &value); + g_value_unset (&value); + } + break; + case ARG_DOUBLE_BUFFER: + if (xvimagesink->have_double_buffer) { + GValue value = { 0 }; + + array = g_value_array_new (2); + g_value_init (&value, G_TYPE_BOOLEAN); + g_value_set_boolean (&value, FALSE); + g_value_array_append (array, &value); + g_value_set_boolean (&value, TRUE); + g_value_array_append (array, &value); + g_value_unset (&value); + } + break; + case ARG_COLORKEY: + if (xvimagesink->have_colorkey) { + GValue value = { 0 }; + + array = g_value_array_new (1); + g_value_init (&value, GST_TYPE_INT_RANGE); + gst_value_set_int_range (&value, 0, 0xffffff); + g_value_array_append (array, &value); + g_value_unset (&value); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); + break; + } + +beach: + return array; +} + +static void +gst_xvboimagesink_property_probe_interface_init (GstPropertyProbeInterface * + iface) +{ + iface->get_properties = gst_xvboimagesink_probe_get_properties; + iface->probe_property = gst_xvboimagesink_probe_probe_property; + iface->needs_probe = gst_xvboimagesink_probe_needs_probe; + iface->get_values = gst_xvboimagesink_probe_get_values; +} + +/* =========================================== */ +/* */ +/* Init & Class init */ +/* */ +/* =========================================== */ + +static void +gst_xvboimagesink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstXvboImageSink *xvimagesink; + + g_return_if_fail (GST_IS_XVIMAGESINK (object)); + + xvimagesink = GST_XVIMAGESINK (object); + + switch (prop_id) { + case ARG_HUE: + xvimagesink->hue = g_value_get_int (value); + xvimagesink->cb_changed = TRUE; + gst_xvboimagesink_update_colorbalance (xvimagesink); + break; + case ARG_CONTRAST: + xvimagesink->contrast = g_value_get_int (value); + xvimagesink->cb_changed = TRUE; + gst_xvboimagesink_update_colorbalance (xvimagesink); + break; + case ARG_BRIGHTNESS: + xvimagesink->brightness = g_value_get_int (value); + xvimagesink->cb_changed = TRUE; + gst_xvboimagesink_update_colorbalance (xvimagesink); + break; + case ARG_SATURATION: + xvimagesink->saturation = g_value_get_int (value); + xvimagesink->cb_changed = TRUE; + gst_xvboimagesink_update_colorbalance (xvimagesink); + break; + case ARG_DISPLAY: + xvimagesink->display_name = g_strdup (g_value_get_string (value)); + break; + case ARG_SYNCHRONOUS: + xvimagesink->synchronous = g_value_get_boolean (value); + if (xvimagesink->xcontext) { + XSynchronize (xvimagesink->xcontext->disp, xvimagesink->synchronous); + GST_DEBUG_OBJECT (xvimagesink, "XSynchronize called with %s", + xvimagesink->synchronous ? "TRUE" : "FALSE"); + } + break; + case ARG_PIXEL_ASPECT_RATIO: + g_free (xvimagesink->par); + xvimagesink->par = g_new0 (GValue, 1); + g_value_init (xvimagesink->par, GST_TYPE_FRACTION); + if (!g_value_transform (value, xvimagesink->par)) { + g_warning ("Could not transform string to aspect ratio"); + gst_value_set_fraction (xvimagesink->par, 1, 1); + } + GST_DEBUG_OBJECT (xvimagesink, "set PAR to %d/%d", + gst_value_get_fraction_numerator (xvimagesink->par), + gst_value_get_fraction_denominator (xvimagesink->par)); + break; + case ARG_FORCE_ASPECT_RATIO: + xvimagesink->keep_aspect = g_value_get_boolean (value); + break; + case ARG_HANDLE_EVENTS: + gst_xvboimagesink_set_event_handling (GST_X_OVERLAY (xvimagesink), + g_value_get_boolean (value)); + gst_xvboimagesink_manage_event_thread (xvimagesink); + break; + case ARG_DEVICE: + xvimagesink->adaptor_no = atoi (g_value_get_string (value)); + break; + case ARG_HANDLE_EXPOSE: + xvimagesink->handle_expose = g_value_get_boolean (value); + gst_xvboimagesink_manage_event_thread (xvimagesink); + break; + case ARG_DOUBLE_BUFFER: + xvimagesink->double_buffer = g_value_get_boolean (value); + break; + case ARG_AUTOPAINT_COLORKEY: + xvimagesink->autopaint_colorkey = g_value_get_boolean (value); + break; + case ARG_COLORKEY: + xvimagesink->colorkey = g_value_get_int (value); + break; + case ARG_DRAW_BORDERS: + xvimagesink->draw_borders = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_xvboimagesink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstXvboImageSink *xvimagesink; + + g_return_if_fail (GST_IS_XVIMAGESINK (object)); + + xvimagesink = GST_XVIMAGESINK (object); + + switch (prop_id) { + case ARG_HUE: + g_value_set_int (value, xvimagesink->hue); + break; + case ARG_CONTRAST: + g_value_set_int (value, xvimagesink->contrast); + break; + case ARG_BRIGHTNESS: + g_value_set_int (value, xvimagesink->brightness); + break; + case ARG_SATURATION: + g_value_set_int (value, xvimagesink->saturation); + break; + case ARG_DISPLAY: + g_value_set_string (value, xvimagesink->display_name); + break; + case ARG_SYNCHRONOUS: + g_value_set_boolean (value, xvimagesink->synchronous); + break; + case ARG_PIXEL_ASPECT_RATIO: + if (xvimagesink->par) + g_value_transform (xvimagesink->par, value); + break; + case ARG_FORCE_ASPECT_RATIO: + g_value_set_boolean (value, xvimagesink->keep_aspect); + break; + case ARG_HANDLE_EVENTS: + g_value_set_boolean (value, xvimagesink->handle_events); + break; + case ARG_DEVICE: + { + char *adaptor_no_s = g_strdup_printf ("%u", xvimagesink->adaptor_no); + + g_value_set_string (value, adaptor_no_s); + g_free (adaptor_no_s); + break; + } + case ARG_DEVICE_NAME: + if (xvimagesink->xcontext && xvimagesink->xcontext->adaptors) { + g_value_set_string (value, + xvimagesink->xcontext->adaptors[xvimagesink->adaptor_no]); + } else { + g_value_set_string (value, NULL); + } + break; + case ARG_HANDLE_EXPOSE: + g_value_set_boolean (value, xvimagesink->handle_expose); + break; + case ARG_DOUBLE_BUFFER: + g_value_set_boolean (value, xvimagesink->double_buffer); + break; + case ARG_AUTOPAINT_COLORKEY: + g_value_set_boolean (value, xvimagesink->autopaint_colorkey); + break; + case ARG_COLORKEY: + g_value_set_int (value, xvimagesink->colorkey); + break; + case ARG_DRAW_BORDERS: + g_value_set_boolean (value, xvimagesink->draw_borders); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_xvboimagesink_reset (GstXvboImageSink * xvimagesink) +{ + GThread *thread; + + GST_LOG_OBJECT (xvimagesink, "BMM Shm buffer reinit\n"); + + gst_xvboimagesink_drm_reinit(xvimagesink); + + GST_OBJECT_LOCK (xvimagesink); + xvimagesink->running = FALSE; + /* grab thread and mark it as NULL */ + thread = xvimagesink->event_thread; + xvimagesink->event_thread = NULL; + GST_OBJECT_UNLOCK (xvimagesink); + + /* invalidate the pool, current allocations continue, new buffer_alloc fails + * with wrong_state */ + g_mutex_lock (xvimagesink->pool_lock); + xvimagesink->pool_invalid = TRUE; + g_mutex_unlock (xvimagesink->pool_lock); + + /* Wait for our event thread to finish before we clean up our stuff. */ + if (thread) + g_thread_join (thread); + + if (xvimagesink->cur_image) { + gst_buffer_unref (GST_BUFFER_CAST (xvimagesink->cur_image)); + xvimagesink->cur_image = NULL; + } + if (xvimagesink->xvimage) { + gst_buffer_unref (GST_BUFFER_CAST (xvimagesink->xvimage)); + xvimagesink->xvimage = NULL; + } + + gst_xvboimagesink_imagepool_clear (xvimagesink); + + if (xvimagesink->xwindow) { + gst_xvboimagesink_xwindow_clear (xvimagesink, xvimagesink->xwindow); + gst_xvboimagesink_xwindow_destroy (xvimagesink, xvimagesink->xwindow); + xvimagesink->xwindow = NULL; + } + + gst_xvboimagesink_xcontext_clear (xvimagesink); +} + +/* Finalize is called only once, dispose can be called multiple times. + * We use mutexes and don't reset stuff to NULL here so let's register + * as a finalize. */ +static void +gst_xvboimagesink_finalize (GObject * object) +{ + GstXvboImageSink *xvimagesink; + + xvimagesink = GST_XVIMAGESINK (object); + + GST_LOG_OBJECT (xvimagesink, "BMM Shm buffer reinit\n"); + + gst_xvboimagesink_drm_reinit(xvimagesink); + + gst_xvboimagesink_reset (xvimagesink); + + if (xvimagesink->display_name) { + g_free (xvimagesink->display_name); + xvimagesink->display_name = NULL; + } + + if (xvimagesink->par) { + g_free (xvimagesink->par); + xvimagesink->par = NULL; + } + if (xvimagesink->x_lock) { + g_mutex_free (xvimagesink->x_lock); + xvimagesink->x_lock = NULL; + } + if (xvimagesink->flow_lock) { + g_mutex_free (xvimagesink->flow_lock); + xvimagesink->flow_lock = NULL; + } + if (xvimagesink->pool_lock) { + g_mutex_free (xvimagesink->pool_lock); + xvimagesink->pool_lock = NULL; + } + + g_free (xvimagesink->media_title); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_xvboimagesink_init (GstXvboImageSink * xvimagesink) +{ + GST_LOG_OBJECT (xvimagesink, "BMM Shm buffer init\n"); + + xvimagesink->drm = NULL; + xvimagesink->drm_ring_idx = 0; + memset(xvimagesink->drm_ring, 0, sizeof(xvimagesink->drm_ring)); + + xvimagesink->display_name = NULL; + xvimagesink->adaptor_no = 0; + xvimagesink->xcontext = NULL; + xvimagesink->xwindow = NULL; + xvimagesink->xvimage = NULL; + xvimagesink->cur_image = NULL; + + xvimagesink->hue = xvimagesink->saturation = 0; + xvimagesink->contrast = xvimagesink->brightness = 0; + xvimagesink->cb_changed = FALSE; + + xvimagesink->fps_n = 0; + xvimagesink->fps_d = 0; + xvimagesink->video_width = 0; + xvimagesink->video_height = 0; + + xvimagesink->x_lock = g_mutex_new (); + xvimagesink->flow_lock = g_mutex_new (); + + xvimagesink->image_pool = NULL; + xvimagesink->pool_lock = g_mutex_new (); + + xvimagesink->synchronous = FALSE; + xvimagesink->double_buffer = TRUE; + xvimagesink->running = FALSE; + xvimagesink->keep_aspect = FALSE; + xvimagesink->handle_events = TRUE; + xvimagesink->par = NULL; + xvimagesink->handle_expose = TRUE; + xvimagesink->autopaint_colorkey = TRUE; + + /* on 16bit displays this becomes r,g,b = 1,2,3 + * on 24bit displays this becomes r,g,b = 8,8,16 + * as a port atom value + */ + xvimagesink->colorkey = (8 << 16) | (8 << 8) | 16; + xvimagesink->draw_borders = TRUE; +} + +static void +gst_xvboimagesink_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details (element_class, &gst_xvboimagesink_details); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_xvboimagesink_sink_template_factory)); +} + +static void +gst_xvboimagesink_class_init (GstXvboImageSinkClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSinkClass *gstbasesink_class; + GstVideoSinkClass *videosink_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesink_class = (GstBaseSinkClass *) klass; + videosink_class = (GstVideoSinkClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = gst_xvboimagesink_set_property; + gobject_class->get_property = gst_xvboimagesink_get_property; + + g_object_class_install_property (gobject_class, ARG_CONTRAST, + g_param_spec_int ("contrast", "Contrast", "The contrast of the video", + -1000, 1000, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_BRIGHTNESS, + g_param_spec_int ("brightness", "Brightness", + "The brightness of the video", -1000, 1000, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_HUE, + g_param_spec_int ("hue", "Hue", "The hue of the video", -1000, 1000, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_SATURATION, + g_param_spec_int ("saturation", "Saturation", + "The saturation of the video", -1000, 1000, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_DISPLAY, + g_param_spec_string ("display", "Display", "X Display name", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_SYNCHRONOUS, + g_param_spec_boolean ("synchronous", "Synchronous", + "When enabled, runs " + "the X display in synchronous mode. (used only for debugging)", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_PIXEL_ASPECT_RATIO, + g_param_spec_string ("pixel-aspect-ratio", "Pixel Aspect Ratio", + "The pixel aspect ratio of the device", "1/1", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_FORCE_ASPECT_RATIO, + g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio", + "When enabled, scaling will respect original aspect ratio", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_HANDLE_EVENTS, + g_param_spec_boolean ("handle-events", "Handle XEvents", + "When enabled, XEvents will be selected and handled", TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_DEVICE, + g_param_spec_string ("device", "Adaptor number", + "The number of the video adaptor", "0", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, ARG_DEVICE_NAME, + g_param_spec_string ("device-name", "Adaptor name", + "The name of the video adaptor", NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + /** + * GstXvboImageSink:handle-expose + * + * When enabled, the current frame will always be drawn in response to X + * Expose. + * + * Since: 0.10.14 + */ + g_object_class_install_property (gobject_class, ARG_HANDLE_EXPOSE, + g_param_spec_boolean ("handle-expose", "Handle expose", + "When enabled, " + "the current frame will always be drawn in response to X Expose " + "events", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstXvboImageSink:double-buffer + * + * Whether to double-buffer the output. + * + * Since: 0.10.14 + */ + g_object_class_install_property (gobject_class, ARG_DOUBLE_BUFFER, + g_param_spec_boolean ("double-buffer", "Double-buffer", + "Whether to double-buffer the output", TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstXvboImageSink:autopaint-colorkey + * + * Whether to autofill overlay with colorkey + * + * Since: 0.10.21 + */ + g_object_class_install_property (gobject_class, ARG_AUTOPAINT_COLORKEY, + g_param_spec_boolean ("autopaint-colorkey", "Autofill with colorkey", + "Whether to autofill overlay with colorkey", TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + /** + * GstXvboImageSink:colorkey + * + * Color to use for the overlay mask. + * + * Since: 0.10.21 + */ + g_object_class_install_property (gobject_class, ARG_COLORKEY, + g_param_spec_int ("colorkey", "Colorkey", + "Color to use for the overlay mask", G_MININT, G_MAXINT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstXvboImageSink:draw-borders + * + * Draw black borders when using GstXvboImageSink:force-aspect-ratio to fill + * unused parts of the video area. + * + * Since: 0.10.21 + */ + g_object_class_install_property (gobject_class, ARG_DRAW_BORDERS, + g_param_spec_boolean ("draw-borders", "Colorkey", + "Draw black borders to fill unused area in force-aspect-ratio mode", + TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gobject_class->finalize = gst_xvboimagesink_finalize; + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_xvboimagesink_change_state); + + gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_xvboimagesink_getcaps); + gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_xvboimagesink_setcaps); + gstbasesink_class->buffer_alloc = + GST_DEBUG_FUNCPTR (gst_xvboimagesink_buffer_alloc); + gstbasesink_class->get_times = GST_DEBUG_FUNCPTR (gst_xvboimagesink_get_times); + gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_xvboimagesink_event); + + videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_xvboimagesink_show_frame); +} + +/* ============================================================= */ +/* */ +/* Public Methods */ +/* */ +/* ============================================================= */ + +/* =========================================== */ +/* */ +/* Object typing & Creation */ +/* */ +/* =========================================== */ + +GType +gst_xvboimagesink_get_type (void) +{ + static GType xvimagesink_type = 0; + + if (!xvimagesink_type) { + static const GTypeInfo xvimagesink_info = { + sizeof (GstXvboImageSinkClass), + gst_xvboimagesink_base_init, + NULL, + (GClassInitFunc) gst_xvboimagesink_class_init, + NULL, + NULL, + sizeof (GstXvboImageSink), + 0, + (GInstanceInitFunc) gst_xvboimagesink_init, + }; + static const GInterfaceInfo iface_info = { + (GInterfaceInitFunc) gst_xvboimagesink_interface_init, + NULL, + NULL, + }; + static const GInterfaceInfo navigation_info = { + (GInterfaceInitFunc) gst_xvboimagesink_navigation_init, + NULL, + NULL, + }; + static const GInterfaceInfo overlay_info = { + (GInterfaceInitFunc) gst_xvboimagesink_xoverlay_init, + NULL, + NULL, + }; + static const GInterfaceInfo colorbalance_info = { + (GInterfaceInitFunc) gst_xvboimagesink_colorbalance_init, + NULL, + NULL, + }; + static const GInterfaceInfo propertyprobe_info = { + (GInterfaceInitFunc) gst_xvboimagesink_property_probe_interface_init, + NULL, + NULL, + }; + xvimagesink_type = g_type_register_static (GST_TYPE_VIDEO_SINK, + "GstXvboImageSink", &xvimagesink_info, 0); + + g_type_add_interface_static (xvimagesink_type, + GST_TYPE_IMPLEMENTS_INTERFACE, &iface_info); + g_type_add_interface_static (xvimagesink_type, GST_TYPE_NAVIGATION, + &navigation_info); + g_type_add_interface_static (xvimagesink_type, GST_TYPE_X_OVERLAY, + &overlay_info); + g_type_add_interface_static (xvimagesink_type, GST_TYPE_COLOR_BALANCE, + &colorbalance_info); + g_type_add_interface_static (xvimagesink_type, GST_TYPE_PROPERTY_PROBE, + &propertyprobe_info); + + + /* register type and create class in a more safe place instead of at + * runtime since the type registration and class creation is not + * threadsafe. */ + g_type_class_ref (gst_xvboimage_buffer_get_type ()); + } + + return xvimagesink_type; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "xvboimagesink", + GST_RANK_PRIMARY + 1, GST_TYPE_XVIMAGESINK)) + return FALSE; + + GST_DEBUG_CATEGORY_INIT (gst_debug_xvboimagesink, "xvboimagesink", 0, + "xvboimagesink element"); + GST_DEBUG_CATEGORY_GET (GST_CAT_PERFORMANCE, "GST_PERFORMANCE"); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "xvboimagesink", + "XFree86 video output plugin using Xv extension with XVBO", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) |