gstreamer/sys/ximage/ximagepool.c
Vincent Penquerc'h 3fd184da78 ximage: do not allocate extra alignment slack for shared memory
A previous patch increased allocations by 15 bytes in order to ensure
16 byte alignment for g_malloc blocks. However, shared memory is
already block aligned, and this extra 15 bytes is not needed. Since
shared memory limits are low compared to RAM, we remove this waste.

https://bugzilla.gnome.org/show_bug.cgi?id=727236
2015-04-03 11:15:55 +01:00

718 lines
20 KiB
C

/* 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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/* Object header */
#include "ximagesink.h"
/* Debugging category */
#include <gst/gstinfo.h>
/* Helper functions */
#include <gst/video/video.h>
#include <gst/video/gstvideometa.h>
#include <gst/video/gstvideopool.h>
GST_DEBUG_CATEGORY_EXTERN (gst_debug_ximagepool);
#define GST_CAT_DEFAULT gst_debug_ximagepool
/* X11 stuff */
static gboolean error_caught = FALSE;
static int
gst_ximagesink_handle_xerror (Display * display, XErrorEvent * xevent)
{
char error_msg[1024];
XGetErrorText (display, xevent->error_code, error_msg, 1024);
GST_DEBUG ("ximagesink triggered an XError. error: %s", error_msg);
error_caught = TRUE;
return 0;
}
static GstMemory *
gst_ximage_memory_alloc (GstAllocator * allocator, gsize size,
GstAllocationParams * params)
{
return NULL;
}
static void
gst_ximage_memory_free (GstAllocator * allocator, GstMemory * gmem)
{
GstXImageMemory *mem = (GstXImageMemory *) gmem;
GstXImageSink *ximagesink;
if (gmem->parent)
goto sub_mem;
ximagesink = mem->sink;
GST_DEBUG_OBJECT (ximagesink, "free memory %p", mem);
/* Hold the object lock to ensure the XContext doesn't disappear */
GST_OBJECT_LOCK (ximagesink);
/* We might have some buffers destroyed after changing state to NULL */
if (ximagesink->xcontext == NULL) {
GST_DEBUG_OBJECT (ximagesink, "Destroying XImage after XContext");
#ifdef HAVE_XSHM
/* Need to free the shared memory segment even if the x context
* was already cleaned up */
if (mem->SHMInfo.shmaddr != ((void *) -1)) {
shmdt (mem->SHMInfo.shmaddr);
}
#endif
goto beach;
}
g_mutex_lock (&ximagesink->x_lock);
#ifdef HAVE_XSHM
if (ximagesink->xcontext->use_xshm) {
if (mem->SHMInfo.shmaddr != ((void *) -1)) {
GST_DEBUG_OBJECT (ximagesink, "XServer ShmDetaching from 0x%x id 0x%lx",
mem->SHMInfo.shmid, mem->SHMInfo.shmseg);
XShmDetach (ximagesink->xcontext->disp, &mem->SHMInfo);
XSync (ximagesink->xcontext->disp, FALSE);
shmdt (mem->SHMInfo.shmaddr);
mem->SHMInfo.shmaddr = (void *) -1;
}
if (mem->ximage)
XDestroyImage (mem->ximage);
} else
#endif /* HAVE_XSHM */
{
if (mem->ximage) {
XDestroyImage (mem->ximage);
}
}
XSync (ximagesink->xcontext->disp, FALSE);
g_mutex_unlock (&ximagesink->x_lock);
beach:
GST_OBJECT_UNLOCK (ximagesink);
gst_object_unref (mem->sink);
sub_mem:
g_slice_free (GstXImageMemory, mem);
}
static gpointer
ximage_memory_map (GstXImageMemory * mem, gsize maxsize, GstMapFlags flags)
{
return mem->ximage->data + mem->parent.offset;
}
static gboolean
ximage_memory_unmap (GstXImageMemory * mem)
{
return TRUE;
}
static GstXImageMemory *
ximage_memory_share (GstXImageMemory * mem, gssize offset, gsize size)
{
GstXImageMemory *sub;
GstMemory *parent;
/* We can only share the complete memory */
if (offset != 0)
return NULL;
if (size != -1 && size != mem->size)
return NULL;
/* find the real parent */
if ((parent = mem->parent.parent) == NULL)
parent = (GstMemory *) mem;
if (size == -1)
size = mem->parent.size - offset;
/* the shared memory is always readonly */
sub = g_slice_new (GstXImageMemory);
gst_memory_init (GST_MEMORY_CAST (sub), GST_MINI_OBJECT_FLAGS (parent) |
GST_MINI_OBJECT_FLAG_LOCK_READONLY, mem->parent.allocator,
&mem->parent, mem->parent.maxsize, mem->parent.align,
mem->parent.offset + offset, size);
sub->sink = mem->sink;
sub->ximage = mem->ximage;
#ifdef HAVE_XSHM
sub->SHMInfo = mem->SHMInfo;
#endif
sub->x = mem->x;
sub->y = mem->y;
sub->width = mem->width;
sub->height = mem->height;
return sub;
}
typedef GstAllocator GstXImageMemoryAllocator;
typedef GstAllocatorClass GstXImageMemoryAllocatorClass;
GType ximage_memory_allocator_get_type (void);
G_DEFINE_TYPE (GstXImageMemoryAllocator, ximage_memory_allocator,
GST_TYPE_ALLOCATOR);
#define GST_XIMAGE_ALLOCATOR_NAME "ximage"
#define GST_TYPE_XIMAGE_MEMORY_ALLOCATOR (ximage_memory_allocator_get_type())
#define GST_IS_XIMAGE_MEMORY_ALLOCATOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_XIMAGE_MEMORY_ALLOCATOR))
static void
ximage_memory_allocator_class_init (GstXImageMemoryAllocatorClass * klass)
{
GstAllocatorClass *allocator_class;
allocator_class = (GstAllocatorClass *) klass;
allocator_class->alloc = gst_ximage_memory_alloc;
allocator_class->free = gst_ximage_memory_free;
}
static void
ximage_memory_allocator_init (GstXImageMemoryAllocator * allocator)
{
GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);
alloc->mem_type = GST_XIMAGE_ALLOCATOR_NAME;
alloc->mem_map = (GstMemoryMapFunction) ximage_memory_map;
alloc->mem_unmap = (GstMemoryUnmapFunction) ximage_memory_unmap;
alloc->mem_share = (GstMemoryShareFunction) ximage_memory_share;
/* fallback copy and is_span */
GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
}
static GstMemory *
ximage_memory_alloc (GstXImageBufferPool * xpool)
{
GstXImageSink *ximagesink;
int (*handler) (Display *, XErrorEvent *);
gboolean success = FALSE;
GstXContext *xcontext;
gint width, height, align, offset;
GstXImageMemory *mem;
ximagesink = xpool->sink;
xcontext = ximagesink->xcontext;
width = xpool->padded_width;
height = xpool->padded_height;
mem = g_slice_new (GstXImageMemory);
#ifdef HAVE_XSHM
mem->SHMInfo.shmaddr = ((void *) -1);
mem->SHMInfo.shmid = -1;
#endif
mem->x = xpool->align.padding_left;
mem->y = xpool->align.padding_top;
mem->width = GST_VIDEO_INFO_WIDTH (&xpool->info);
mem->height = GST_VIDEO_INFO_HEIGHT (&xpool->info);
mem->sink = gst_object_ref (ximagesink);
GST_DEBUG_OBJECT (ximagesink, "creating image %p (%dx%d)", mem,
width, height);
g_mutex_lock (&ximagesink->x_lock);
/* Setting an error handler to catch failure */
error_caught = FALSE;
handler = XSetErrorHandler (gst_ximagesink_handle_xerror);
#ifdef HAVE_XSHM
if (xcontext->use_xshm) {
mem->ximage = XShmCreateImage (xcontext->disp,
xcontext->visual,
xcontext->depth, ZPixmap, NULL, &mem->SHMInfo, width, height);
if (!mem->ximage || error_caught) {
g_mutex_unlock (&ximagesink->x_lock);
/* Reset error flag */
error_caught = FALSE;
/* Push a warning */
GST_ELEMENT_WARNING (ximagesink, RESOURCE, WRITE,
("Failed to create output image buffer of %dx%d pixels",
width, height),
("could not XShmCreateImage a %dx%d image", width, height));
/* Retry without XShm */
ximagesink->xcontext->use_xshm = FALSE;
/* Hold X mutex again to try without XShm */
g_mutex_lock (&ximagesink->x_lock);
goto no_xshm;
}
/* we have to use the returned bytes_per_line for our shm size */
mem->size = mem->ximage->bytes_per_line * mem->ximage->height;
GST_LOG_OBJECT (ximagesink,
"XShm image size is %" G_GSIZE_FORMAT ", width %d, stride %d",
mem->size, width, mem->ximage->bytes_per_line);
/* get shared memory */
align = 0;
mem->SHMInfo.shmid =
shmget (IPC_PRIVATE, mem->size + align, IPC_CREAT | 0777);
if (mem->SHMInfo.shmid == -1)
goto shmget_failed;
/* attach */
mem->SHMInfo.shmaddr = shmat (mem->SHMInfo.shmid, NULL, 0);
if (mem->SHMInfo.shmaddr == ((void *) -1))
goto shmat_failed;
/* now we can set up the image data */
mem->ximage->data = mem->SHMInfo.shmaddr;
mem->SHMInfo.readOnly = FALSE;
if (XShmAttach (xcontext->disp, &mem->SHMInfo) == 0)
goto xattach_failed;
XSync (xcontext->disp, FALSE);
/* Now that everyone has attached, we can delete the shared memory segment.
* This way, it will be deleted as soon as we detach later, and not
* leaked if we crash. */
shmctl (mem->SHMInfo.shmid, IPC_RMID, NULL);
GST_DEBUG_OBJECT (ximagesink, "XServer ShmAttached to 0x%x, id 0x%lx",
mem->SHMInfo.shmid, mem->SHMInfo.shmseg);
} else
no_xshm:
#endif /* HAVE_XSHM */
{
guint allocsize;
mem->ximage = XCreateImage (xcontext->disp,
xcontext->visual,
xcontext->depth, ZPixmap, 0, NULL, width, height, xcontext->bpp, 0);
if (!mem->ximage || error_caught)
goto create_failed;
/* upstream will assume that rowstrides are multiples of 4, but this
* doesn't always seem to be the case with XCreateImage() */
if ((mem->ximage->bytes_per_line % 4) != 0) {
GST_WARNING_OBJECT (ximagesink, "returned stride not a multiple of 4 as "
"usually assumed");
}
/* we have to use the returned bytes_per_line for our image size */
mem->size = mem->ximage->bytes_per_line * mem->ximage->height;
/* alloc a bit more for unexpected strides to avoid crashes upstream.
* FIXME: if we get an unrounded stride, the image will be displayed
* distorted, since all upstream elements assume a rounded stride */
allocsize =
GST_ROUND_UP_4 (mem->ximage->bytes_per_line) * mem->ximage->height;
/* we want 16 byte aligned memory, g_malloc may only give 8 */
align = 15;
mem->ximage->data = g_malloc (allocsize + align);
GST_LOG_OBJECT (ximagesink,
"non-XShm image size is %" G_GSIZE_FORMAT " (alloced: %u), width %d, "
"stride %d", mem->size, allocsize, width, mem->ximage->bytes_per_line);
XSync (xcontext->disp, FALSE);
}
if ((offset = ((guintptr) mem->ximage->data & align)))
offset = (align + 1) - offset;
GST_DEBUG_OBJECT (ximagesink, "memory %p, align %d, offset %d",
mem->ximage->data, align, offset);
/* Reset error handler */
error_caught = FALSE;
XSetErrorHandler (handler);
gst_memory_init (GST_MEMORY_CAST (mem), GST_MEMORY_FLAG_NO_SHARE,
xpool->allocator, NULL, mem->size + align, align, offset, mem->size);
g_mutex_unlock (&ximagesink->x_lock);
success = TRUE;
beach:
if (!success) {
g_slice_free (GstXImageMemory, mem);
mem = NULL;
}
return GST_MEMORY_CAST (mem);
/* ERRORS */
create_failed:
{
g_mutex_unlock (&ximagesink->x_lock);
/* Reset error handler */
error_caught = FALSE;
XSetErrorHandler (handler);
/* Push an error */
GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
("Failed to create output image buffer of %dx%d pixels",
width, height),
("could not XShmCreateImage a %dx%d image", width, height));
goto beach;
}
#ifdef HAVE_XSHM
shmget_failed:
{
g_mutex_unlock (&ximagesink->x_lock);
GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
("Failed to create output image buffer of %dx%d pixels",
width, height),
("could not get shared memory of %" G_GSIZE_FORMAT " bytes",
mem->size));
goto beach;
}
shmat_failed:
{
g_mutex_unlock (&ximagesink->x_lock);
GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
("Failed to create output image buffer of %dx%d pixels",
width, height), ("Failed to shmat: %s", g_strerror (errno)));
/* Clean up the shared memory segment */
shmctl (mem->SHMInfo.shmid, IPC_RMID, NULL);
goto beach;
}
xattach_failed:
{
/* Clean up the shared memory segment */
shmctl (mem->SHMInfo.shmid, IPC_RMID, NULL);
g_mutex_unlock (&ximagesink->x_lock);
GST_ELEMENT_ERROR (ximagesink, RESOURCE, WRITE,
("Failed to create output image buffer of %dx%d pixels",
width, height), ("Failed to XShmAttach"));
goto beach;
}
#endif
}
#ifdef HAVE_XSHM
/* This function checks that it is actually really possible to create an image
using XShm */
gboolean
gst_ximagesink_check_xshm_calls (GstXImageSink * ximagesink,
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 (gst_ximagesink_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, 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;
}
ximage->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 (ximage)
XDestroyImage (ximage);
return result;
}
#endif /* HAVE_XSHM */
/* bufferpool */
static void gst_ximage_buffer_pool_finalize (GObject * object);
#define gst_ximage_buffer_pool_parent_class parent_class
G_DEFINE_TYPE (GstXImageBufferPool, gst_ximage_buffer_pool,
GST_TYPE_BUFFER_POOL);
static const gchar **
ximage_buffer_pool_get_options (GstBufferPool * pool)
{
static const gchar *options[] = { GST_BUFFER_POOL_OPTION_VIDEO_META,
GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT, NULL
};
return options;
}
static gboolean
ximage_buffer_pool_set_config (GstBufferPool * pool, GstStructure * config)
{
GstXImageBufferPool *xpool = GST_XIMAGE_BUFFER_POOL_CAST (pool);
GstVideoInfo info;
GstCaps *caps;
guint size, min_buffers, max_buffers;
if (!gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers,
&max_buffers))
goto wrong_config;
if (caps == NULL)
goto no_caps;
/* now parse the caps from the config */
if (!gst_video_info_from_caps (&info, caps))
goto wrong_caps;
GST_LOG_OBJECT (pool, "%dx%d, caps %" GST_PTR_FORMAT, info.width, info.height,
caps);
/* keep track of the width and height and caps */
if (xpool->caps)
gst_caps_unref (xpool->caps);
xpool->caps = gst_caps_ref (caps);
/* check for the configured metadata */
xpool->add_metavideo =
gst_buffer_pool_config_has_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_META);
/* parse extra alignment info */
xpool->need_alignment = gst_buffer_pool_config_has_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
if (xpool->need_alignment) {
gst_buffer_pool_config_get_video_alignment (config, &xpool->align);
GST_LOG_OBJECT (pool, "padding %u-%ux%u-%u", xpool->align.padding_top,
xpool->align.padding_left, xpool->align.padding_left,
xpool->align.padding_bottom);
/* do padding and alignment */
gst_video_info_align (&info, &xpool->align);
gst_buffer_pool_config_set_video_alignment (config, &xpool->align);
/* we need the video metadata too now */
xpool->add_metavideo = TRUE;
} else {
gst_video_alignment_reset (&xpool->align);
}
/* add the padding */
xpool->padded_width =
GST_VIDEO_INFO_WIDTH (&info) + xpool->align.padding_left +
xpool->align.padding_right;
xpool->padded_height =
GST_VIDEO_INFO_HEIGHT (&info) + xpool->align.padding_top +
xpool->align.padding_bottom;
xpool->info = info;
gst_buffer_pool_config_set_params (config, caps, info.size, min_buffers,
max_buffers);
return GST_BUFFER_POOL_CLASS (parent_class)->set_config (pool, config);
/* ERRORS */
wrong_config:
{
GST_WARNING_OBJECT (pool, "invalid config");
return FALSE;
}
no_caps:
{
GST_WARNING_OBJECT (pool, "no caps in config");
return FALSE;
}
wrong_caps:
{
GST_WARNING_OBJECT (pool,
"failed getting geometry from caps %" GST_PTR_FORMAT, caps);
return FALSE;
}
}
/* This function handles GstXImageBuffer creation depending on XShm availability */
static GstFlowReturn
ximage_buffer_pool_alloc (GstBufferPool * pool, GstBuffer ** buffer,
GstBufferPoolAcquireParams * params)
{
GstXImageBufferPool *xpool = GST_XIMAGE_BUFFER_POOL_CAST (pool);
GstVideoInfo *info;
GstBuffer *ximage;
GstMemory *mem;
info = &xpool->info;
ximage = gst_buffer_new ();
mem = ximage_memory_alloc (xpool);
if (mem == NULL) {
gst_buffer_unref (ximage);
goto no_buffer;
}
gst_buffer_append_memory (ximage, mem);
if (xpool->add_metavideo) {
GST_DEBUG_OBJECT (pool, "adding GstVideoMeta");
/* these are just the defaults for now */
gst_buffer_add_video_meta_full (ximage, GST_VIDEO_FRAME_FLAG_NONE,
GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info),
GST_VIDEO_INFO_HEIGHT (info), GST_VIDEO_INFO_N_PLANES (info),
info->offset, info->stride);
}
*buffer = ximage;
return GST_FLOW_OK;
/* ERROR */
no_buffer:
{
GST_WARNING_OBJECT (pool, "can't create image");
return GST_FLOW_ERROR;
}
}
GstBufferPool *
gst_ximage_buffer_pool_new (GstXImageSink * ximagesink)
{
GstXImageBufferPool *pool;
g_return_val_if_fail (GST_IS_XIMAGESINK (ximagesink), NULL);
pool = g_object_new (GST_TYPE_XIMAGE_BUFFER_POOL, NULL);
pool->sink = gst_object_ref (ximagesink);
pool->allocator = g_object_new (GST_TYPE_XIMAGE_MEMORY_ALLOCATOR, NULL);
GST_LOG_OBJECT (pool, "new XImage buffer pool %p", pool);
return GST_BUFFER_POOL_CAST (pool);
}
static void
gst_ximage_buffer_pool_class_init (GstXImageBufferPoolClass * klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstBufferPoolClass *gstbufferpool_class = (GstBufferPoolClass *) klass;
gobject_class->finalize = gst_ximage_buffer_pool_finalize;
gstbufferpool_class->get_options = ximage_buffer_pool_get_options;
gstbufferpool_class->set_config = ximage_buffer_pool_set_config;
gstbufferpool_class->alloc_buffer = ximage_buffer_pool_alloc;
}
static void
gst_ximage_buffer_pool_init (GstXImageBufferPool * pool)
{
/* nothing to do here */
}
static void
gst_ximage_buffer_pool_finalize (GObject * object)
{
GstXImageBufferPool *pool = GST_XIMAGE_BUFFER_POOL_CAST (object);
GST_LOG_OBJECT (pool, "finalize XImage buffer pool %p", pool);
if (pool->caps)
gst_caps_unref (pool->caps);
gst_object_unref (pool->sink);
gst_object_unref (pool->allocator);
G_OBJECT_CLASS (gst_ximage_buffer_pool_parent_class)->finalize (object);
}