| /* GStreamer |
| * Copyright (C) <2005> Luca Ognibene <luogni@tin.it> |
| * |
| * 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., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "ximageutil.h" |
| |
| GType |
| gst_meta_ximage_api_get_type (void) |
| { |
| static volatile GType type; |
| static const gchar *tags[] = { "memory", NULL }; |
| |
| if (g_once_init_enter (&type)) { |
| GType _type = gst_meta_api_type_register ("GstMetaXImageSrcAPI", tags); |
| g_once_init_leave (&type, _type); |
| } |
| return type; |
| } |
| |
| static gboolean |
| gst_meta_ximage_init (GstMeta * meta, gpointer params, GstBuffer * buffer) |
| { |
| GstMetaXImage *emeta = (GstMetaXImage *) meta; |
| |
| emeta->parent = NULL; |
| emeta->ximage = NULL; |
| #ifdef HAVE_XSHM |
| emeta->SHMInfo.shmaddr = ((void *) -1); |
| emeta->SHMInfo.shmid = -1; |
| emeta->SHMInfo.readOnly = TRUE; |
| #endif |
| emeta->width = emeta->height = emeta->size = 0; |
| emeta->return_func = NULL; |
| |
| return TRUE; |
| } |
| |
| const GstMetaInfo * |
| gst_meta_ximage_get_info (void) |
| { |
| static const GstMetaInfo *meta_ximage_info = NULL; |
| |
| if (g_once_init_enter (&meta_ximage_info)) { |
| const GstMetaInfo *meta = |
| gst_meta_register (gst_meta_ximage_api_get_type (), "GstMetaXImageSrc", |
| sizeof (GstMetaXImage), (GstMetaInitFunction) gst_meta_ximage_init, |
| (GstMetaFreeFunction) NULL, (GstMetaTransformFunction) NULL); |
| g_once_init_leave (&meta_ximage_info, meta); |
| } |
| return meta_ximage_info; |
| } |
| |
| #ifdef HAVE_XSHM |
| static gboolean error_caught = FALSE; |
| |
| static int |
| ximageutil_handle_xerror (Display * display, XErrorEvent * xevent) |
| { |
| char error_msg[1024]; |
| |
| XGetErrorText (display, xevent->error_code, error_msg, 1024); |
| GST_DEBUG ("ximageutil failed to use XShm calls. error: %s", error_msg); |
| error_caught = TRUE; |
| return 0; |
| } |
| |
| /* This function checks that it is actually really possible to create an image |
| using XShm */ |
| gboolean |
| ximageutil_check_xshm_calls (GstXContext * xcontext) |
| { |
| XImage *ximage; |
| XShmSegmentInfo SHMInfo; |
| size_t 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 (ximageutil_handle_xerror); |
| |
| /* Trying to create a 1x1 ximage */ |
| GST_DEBUG ("XShmCreateImage of 1x1"); |
| |
| ximage = XShmCreateImage (xcontext->disp, xcontext->visual, |
| xcontext->depth, ZPixmap, NULL, &SHMInfo, 1, 1); |
| |
| /* Might cause an error, sync to ensure it is noticed */ |
| XSync (xcontext->disp, FALSE); |
| if (!ximage || error_caught) { |
| GST_WARNING ("could not XShmCreateImage a 1x1 image"); |
| goto beach; |
| } |
| size = ximage->height * ximage->bytes_per_line; |
| |
| SHMInfo.shmid = shmget (IPC_PRIVATE, size, IPC_CREAT | 0777); |
| if (SHMInfo.shmid == -1) { |
| GST_WARNING ("could not get shared memory of %" G_GSIZE_FORMAT " bytes", |
| size); |
| goto beach; |
| } |
| |
| SHMInfo.shmaddr = shmat (SHMInfo.shmid, 0, 0); |
| if (SHMInfo.shmaddr == ((void *) -1)) { |
| GST_WARNING ("Failed to shmat: %s", g_strerror (errno)); |
| goto beach; |
| } |
| |
| /* Delete the SHM segment. It will actually go away automatically |
| * when we detach now */ |
| shmctl (SHMInfo.shmid, IPC_RMID, 0); |
| |
| ximage->data = SHMInfo.shmaddr; |
| SHMInfo.readOnly = FALSE; |
| |
| if (XShmAttach (xcontext->disp, &SHMInfo) == 0) { |
| GST_WARNING ("Failed to XShmAttach"); |
| goto beach; |
| } |
| |
| /* Sync to ensure we see any errors we caused */ |
| XSync (xcontext->disp, FALSE); |
| |
| if (!error_caught) { |
| did_attach = TRUE; |
| /* store whether we succeeded in result */ |
| result = TRUE; |
| } |
| 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) { |
| XShmDetach (xcontext->disp, &SHMInfo); |
| XSync (xcontext->disp, FALSE); |
| } |
| if (SHMInfo.shmaddr != ((void *) -1)) |
| shmdt (SHMInfo.shmaddr); |
| if (ximage) |
| XDestroyImage (ximage); |
| return result; |
| } |
| #endif /* HAVE_XSHM */ |
| |
| /* 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 */ |
| GstXContext * |
| ximageutil_xcontext_get (GstElement * parent, const gchar * display_name) |
| { |
| GstXContext *xcontext = NULL; |
| XPixmapFormatValues *px_formats = NULL; |
| gint nb_formats = 0, i; |
| |
| xcontext = g_new0 (GstXContext, 1); |
| |
| xcontext->disp = XOpenDisplay (display_name); |
| GST_DEBUG_OBJECT (parent, "opened display %p", xcontext->disp); |
| if (!xcontext->disp) { |
| g_free (xcontext); |
| return NULL; |
| } |
| xcontext->screen = DefaultScreenOfDisplay (xcontext->disp); |
| xcontext->visual = DefaultVisualOfScreen (xcontext->screen); |
| xcontext->root = RootWindowOfScreen (xcontext->screen); |
| xcontext->white = WhitePixelOfScreen (xcontext->screen); |
| xcontext->black = BlackPixelOfScreen (xcontext->screen); |
| xcontext->depth = DefaultDepthOfScreen (xcontext->screen); |
| |
| xcontext->width = WidthOfScreen (xcontext->screen); |
| xcontext->height = HeightOfScreen (xcontext->screen); |
| |
| xcontext->widthmm = WidthMMOfScreen (xcontext->screen); |
| xcontext->heightmm = HeightMMOfScreen (xcontext->screen); |
| |
| xcontext->caps = NULL; |
| |
| GST_DEBUG_OBJECT (parent, "X reports %dx%d pixels and %d mm x %d mm", |
| xcontext->width, xcontext->height, xcontext->widthmm, xcontext->heightmm); |
| ximageutil_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_free (xcontext); |
| 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; |
| |
| #ifdef HAVE_XSHM |
| /* Search for XShm extension support */ |
| if (XShmQueryExtension (xcontext->disp) && |
| ximageutil_check_xshm_calls (xcontext)) { |
| xcontext->use_xshm = TRUE; |
| GST_DEBUG ("ximageutil is using XShm extension"); |
| } else { |
| xcontext->use_xshm = FALSE; |
| GST_DEBUG ("ximageutil is not using XShm extension"); |
| } |
| #endif /* HAVE_XSHM */ |
| |
| /* 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->r_mask_output = GUINT32_TO_BE (xcontext->visual->red_mask); |
| xcontext->g_mask_output = GUINT32_TO_BE (xcontext->visual->green_mask); |
| xcontext->b_mask_output = GUINT32_TO_BE (xcontext->visual->blue_mask); |
| if (xcontext->bpp == 24) { |
| xcontext->r_mask_output >>= 8; |
| xcontext->g_mask_output >>= 8; |
| xcontext->b_mask_output >>= 8; |
| } |
| } else { |
| xcontext->r_mask_output = xcontext->visual->red_mask; |
| xcontext->g_mask_output = xcontext->visual->green_mask; |
| xcontext->b_mask_output = xcontext->visual->blue_mask; |
| } |
| |
| return xcontext; |
| } |
| |
| /* This function cleans the X context. Closing the Display and unrefing the |
| caps for supported formats. */ |
| void |
| ximageutil_xcontext_clear (GstXContext * xcontext) |
| { |
| g_return_if_fail (xcontext != NULL); |
| |
| if (xcontext->caps != NULL) |
| gst_caps_unref (xcontext->caps); |
| |
| XCloseDisplay (xcontext->disp); |
| |
| g_free (xcontext); |
| } |
| |
| /* This function calculates the pixel aspect ratio based on the properties |
| * in the xcontext structure and stores it there. */ |
| void |
| ximageutil_calculate_pixel_aspect_ratio (GstXContext * xcontext) |
| { |
| 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 */ |
| }; |
| 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]); |
| |
| xcontext->par_n = par[index][0]; |
| xcontext->par_d = par[index][1]; |
| GST_DEBUG ("set xcontext PAR to %d/%d\n", xcontext->par_n, xcontext->par_d); |
| } |
| |
| static gboolean |
| gst_ximagesrc_buffer_dispose (GstBuffer * ximage) |
| { |
| GstElement *parent; |
| GstMetaXImage *meta; |
| gboolean ret = TRUE; |
| |
| meta = GST_META_XIMAGE_GET (ximage); |
| |
| parent = meta->parent; |
| if (parent == NULL) { |
| g_warning ("XImageSrcBuffer->ximagesrc == NULL"); |
| goto beach; |
| } |
| |
| if (meta->return_func) |
| ret = meta->return_func (parent, ximage); |
| |
| beach: |
| return ret; |
| } |
| |
| void |
| gst_ximage_buffer_free (GstBuffer * ximage) |
| { |
| GstMetaXImage *meta; |
| |
| meta = GST_META_XIMAGE_GET (ximage); |
| |
| /* make sure it is not recycled */ |
| meta->width = -1; |
| meta->height = -1; |
| gst_buffer_unref (ximage); |
| } |
| |
| /* This function handles GstXImageSrcBuffer creation depending on XShm availability */ |
| GstBuffer * |
| gst_ximageutil_ximage_new (GstXContext * xcontext, |
| GstElement * parent, int width, int height, BufferReturnFunc return_func) |
| { |
| GstBuffer *ximage = NULL; |
| GstMetaXImage *meta; |
| gboolean succeeded = FALSE; |
| |
| ximage = gst_buffer_new (); |
| GST_MINI_OBJECT_CAST (ximage)->dispose = |
| (GstMiniObjectDisposeFunction) gst_ximagesrc_buffer_dispose; |
| |
| meta = GST_META_XIMAGE_ADD (ximage); |
| meta->width = width; |
| meta->height = height; |
| |
| #ifdef HAVE_XSHM |
| meta->SHMInfo.shmaddr = ((void *) -1); |
| meta->SHMInfo.shmid = -1; |
| |
| if (xcontext->use_xshm) { |
| meta->ximage = XShmCreateImage (xcontext->disp, |
| xcontext->visual, xcontext->depth, |
| ZPixmap, NULL, &meta->SHMInfo, meta->width, meta->height); |
| if (!meta->ximage) { |
| GST_WARNING_OBJECT (parent, |
| "could not XShmCreateImage a %dx%d image", meta->width, meta->height); |
| |
| /* Retry without XShm */ |
| xcontext->use_xshm = FALSE; |
| goto no_xshm; |
| } |
| |
| /* we have to use the returned bytes_per_line for our shm size */ |
| meta->size = meta->ximage->bytes_per_line * meta->ximage->height; |
| meta->SHMInfo.shmid = shmget (IPC_PRIVATE, meta->size, IPC_CREAT | 0777); |
| if (meta->SHMInfo.shmid == -1) |
| goto beach; |
| |
| meta->SHMInfo.shmaddr = shmat (meta->SHMInfo.shmid, 0, 0); |
| if (meta->SHMInfo.shmaddr == ((void *) -1)) |
| goto beach; |
| |
| /* Delete the SHM segment. It will actually go away automatically |
| * when we detach now */ |
| shmctl (meta->SHMInfo.shmid, IPC_RMID, 0); |
| |
| meta->ximage->data = meta->SHMInfo.shmaddr; |
| meta->SHMInfo.readOnly = FALSE; |
| |
| if (XShmAttach (xcontext->disp, &meta->SHMInfo) == 0) |
| goto beach; |
| |
| XSync (xcontext->disp, FALSE); |
| } else |
| no_xshm: |
| #endif /* HAVE_XSHM */ |
| { |
| meta->ximage = XCreateImage (xcontext->disp, |
| xcontext->visual, |
| xcontext->depth, |
| ZPixmap, 0, NULL, meta->width, meta->height, xcontext->bpp, 0); |
| if (!meta->ximage) |
| goto beach; |
| |
| /* we have to use the returned bytes_per_line for our image size */ |
| meta->size = meta->ximage->bytes_per_line * meta->ximage->height; |
| meta->ximage->data = g_malloc (meta->size); |
| |
| XSync (xcontext->disp, FALSE); |
| } |
| succeeded = TRUE; |
| |
| gst_buffer_append_memory (ximage, |
| gst_memory_new_wrapped (GST_MEMORY_FLAG_NO_SHARE, meta->ximage->data, |
| meta->size, 0, meta->size, NULL, NULL)); |
| |
| /* Keep a ref to our src */ |
| meta->parent = gst_object_ref (parent); |
| meta->return_func = return_func; |
| beach: |
| if (!succeeded) { |
| gst_ximage_buffer_free (ximage); |
| ximage = NULL; |
| } |
| |
| return ximage; |
| } |
| |
| /* This function destroys a GstXImageBuffer handling XShm availability */ |
| void |
| gst_ximageutil_ximage_destroy (GstXContext * xcontext, GstBuffer * ximage) |
| { |
| GstMetaXImage *meta; |
| |
| meta = GST_META_XIMAGE_GET (ximage); |
| |
| /* We might have some buffers destroyed after changing state to NULL */ |
| if (!xcontext) |
| goto beach; |
| |
| g_return_if_fail (ximage != NULL); |
| |
| #ifdef HAVE_XSHM |
| if (xcontext->use_xshm) { |
| if (meta->SHMInfo.shmaddr != ((void *) -1)) { |
| XShmDetach (xcontext->disp, &meta->SHMInfo); |
| XSync (xcontext->disp, 0); |
| shmdt (meta->SHMInfo.shmaddr); |
| } |
| if (meta->ximage) |
| XDestroyImage (meta->ximage); |
| |
| } else |
| #endif /* HAVE_XSHM */ |
| { |
| if (meta->ximage) { |
| XDestroyImage (meta->ximage); |
| } |
| } |
| |
| XSync (xcontext->disp, FALSE); |
| beach: |
| if (meta->parent) { |
| /* Release the ref to our parent */ |
| gst_object_unref (meta->parent); |
| meta->parent = NULL; |
| } |
| |
| return; |
| } |