mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-24 18:51:11 +00:00
c5c7e7e084
ximage metadata can't be transformed or copied, but provide an empty transformation function instead of NULL to allow unconditional calling of metas' transform functions. https://bugzilla.gnome.org/show_bug.cgi?id=751778
491 lines
14 KiB
C
491 lines
14 KiB
C
/* 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_transform (GstBuffer * dest, GstMeta * meta,
|
|
GstBuffer * buffer, GQuark type, gpointer data)
|
|
{
|
|
/* ximage metadata can't be transformed or copied */
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
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) NULL,
|
|
(GstMetaFreeFunction) NULL, gst_meta_ximage_transform);
|
|
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;
|
|
}
|