gstreamer/omx/gstomxbufferpool.c
Josep Torra 100e9f998d omxbufferpool: return buffers to the pool instead of freeing them
We have to return the buffers back to the pool in when stopping to
not mess with the GstBufferPool accounting.
The OMX buffers will be freed when those won't be in charge of the
pool in the chained up call to 'stop'.
Fixes segfaults on finalize and pool not being properly deactivated.

https://bugzilla.gnome.org/show_bug.cgi?id=726337
2014-03-24 18:20:51 +00:00

585 lines
18 KiB
C

/*
* Copyright (C) 2011, Hewlett-Packard Development Company, L.P.
* Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, Collabora Ltd.
* Copyright (C) 2013, Collabora Ltd.
* Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation
* version 2.1 of the License.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstomxbufferpool.h"
GST_DEBUG_CATEGORY_STATIC (gst_omx_buffer_pool_debug_category);
#define GST_CAT_DEFAULT gst_omx_buffer_pool_debug_category
typedef struct _GstOMXMemory GstOMXMemory;
typedef struct _GstOMXMemoryAllocator GstOMXMemoryAllocator;
typedef struct _GstOMXMemoryAllocatorClass GstOMXMemoryAllocatorClass;
struct _GstOMXMemory
{
GstMemory mem;
GstOMXBuffer *buf;
};
struct _GstOMXMemoryAllocator
{
GstAllocator parent;
};
struct _GstOMXMemoryAllocatorClass
{
GstAllocatorClass parent_class;
};
#define GST_OMX_MEMORY_TYPE "openmax"
static GstMemory *
gst_omx_memory_allocator_alloc_dummy (GstAllocator * allocator, gsize size,
GstAllocationParams * params)
{
g_assert_not_reached ();
return NULL;
}
static void
gst_omx_memory_allocator_free (GstAllocator * allocator, GstMemory * mem)
{
GstOMXMemory *omem = (GstOMXMemory *) mem;
/* TODO: We need to remember which memories are still used
* so we can wait until everything is released before allocating
* new memory
*/
g_slice_free (GstOMXMemory, omem);
}
static gpointer
gst_omx_memory_map (GstMemory * mem, gsize maxsize, GstMapFlags flags)
{
GstOMXMemory *omem = (GstOMXMemory *) mem;
return omem->buf->omx_buf->pBuffer + omem->mem.offset;
}
static void
gst_omx_memory_unmap (GstMemory * mem)
{
}
static GstMemory *
gst_omx_memory_share (GstMemory * mem, gssize offset, gssize size)
{
g_assert_not_reached ();
return NULL;
}
GType gst_omx_memory_allocator_get_type (void);
G_DEFINE_TYPE (GstOMXMemoryAllocator, gst_omx_memory_allocator,
GST_TYPE_ALLOCATOR);
#define GST_TYPE_OMX_MEMORY_ALLOCATOR (gst_omx_memory_allocator_get_type())
#define GST_IS_OMX_MEMORY_ALLOCATOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_OMX_MEMORY_ALLOCATOR))
static void
gst_omx_memory_allocator_class_init (GstOMXMemoryAllocatorClass * klass)
{
GstAllocatorClass *allocator_class;
allocator_class = (GstAllocatorClass *) klass;
allocator_class->alloc = gst_omx_memory_allocator_alloc_dummy;
allocator_class->free = gst_omx_memory_allocator_free;
}
static void
gst_omx_memory_allocator_init (GstOMXMemoryAllocator * allocator)
{
GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);
alloc->mem_type = GST_OMX_MEMORY_TYPE;
alloc->mem_map = gst_omx_memory_map;
alloc->mem_unmap = gst_omx_memory_unmap;
alloc->mem_share = gst_omx_memory_share;
/* default copy & is_span */
GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
}
static GstMemory *
gst_omx_memory_allocator_alloc (GstAllocator * allocator, GstMemoryFlags flags,
GstOMXBuffer * buf)
{
GstOMXMemory *mem;
/* FIXME: We don't allow sharing because we need to know
* when the memory becomes unused and can only then put
* it back to the pool. Which is done in the pool's release
* function
*/
flags |= GST_MEMORY_FLAG_NO_SHARE;
mem = g_slice_new (GstOMXMemory);
/* the shared memory is always readonly */
gst_memory_init (GST_MEMORY_CAST (mem), flags, allocator, NULL,
buf->omx_buf->nAllocLen, buf->port->port_def.nBufferAlignment,
0, buf->omx_buf->nAllocLen);
mem->buf = buf;
return GST_MEMORY_CAST (mem);
}
/* Buffer pool for the buffers of an OpenMAX port.
*
* This pool is only used if we either passed buffers from another
* pool to the OMX port or provide the OMX buffers directly to other
* elements.
*
*
* A buffer is in the pool if it is currently owned by the port,
* i.e. after OMX_{Fill,Empty}ThisBuffer(). A buffer is outside
* the pool after it was taken from the port after it was handled
* by the port, i.e. {Empty,Fill}BufferDone.
*
* Buffers can be allocated by us (OMX_AllocateBuffer()) or allocated
* by someone else and (temporarily) passed to this pool
* (OMX_UseBuffer(), OMX_UseEGLImage()). In the latter case the pool of
* the buffer will be overriden, and restored in free_buffer(). Other
* buffers are just freed there.
*
* The pool always has a fixed number of minimum and maximum buffers
* and these are allocated while starting the pool and released afterwards.
* They correspond 1:1 to the OMX buffers of the port, which are allocated
* before the pool is started.
*
* Acquiring a buffer from this pool happens after the OMX buffer has
* been acquired from the port. gst_buffer_pool_acquire_buffer() is
* supposed to return the buffer that corresponds to the OMX buffer.
*
* For buffers provided to upstream, the buffer will be passed to
* the component manually when it arrives and then unreffed. If the
* buffer is released before reaching the component it will be just put
* back into the pool as if EmptyBufferDone has happened. If it was
* passed to the component, it will be back into the pool when it was
* released and EmptyBufferDone has happened.
*
* For buffers provided to downstream, the buffer will be returned
* back to the component (OMX_FillThisBuffer()) when it is released.
*/
static GQuark gst_omx_buffer_data_quark = 0;
G_DEFINE_TYPE (GstOMXBufferPool, gst_omx_buffer_pool, GST_TYPE_BUFFER_POOL);
static void gst_omx_buffer_pool_free_buffer (GstBufferPool * bpool,
GstBuffer * buffer);
static gboolean
gst_omx_buffer_pool_start (GstBufferPool * bpool)
{
GstOMXBufferPool *pool = GST_OMX_BUFFER_POOL (bpool);
/* Only allow to start the pool if we still are attached
* to a component and port */
GST_OBJECT_LOCK (pool);
if (!pool->component || !pool->port) {
GST_OBJECT_UNLOCK (pool);
return FALSE;
}
GST_OBJECT_UNLOCK (pool);
return
GST_BUFFER_POOL_CLASS (gst_omx_buffer_pool_parent_class)->start (bpool);
}
static gboolean
gst_omx_buffer_pool_stop (GstBufferPool * bpool)
{
GstOMXBufferPool *pool = GST_OMX_BUFFER_POOL (bpool);
gint i = 0;
/* When not using the default GstBufferPool::GstAtomicQueue then
* GstBufferPool::free_buffer is not called while stopping the pool
* (because the queue is empty) */
for (i = 0; i < pool->buffers->len; i++)
GST_BUFFER_POOL_CLASS (gst_omx_buffer_pool_parent_class)->release_buffer
(bpool, g_ptr_array_index (pool->buffers, i));
/* Remove any buffers that are there */
g_ptr_array_set_size (pool->buffers, 0);
if (pool->caps)
gst_caps_unref (pool->caps);
pool->caps = NULL;
pool->add_videometa = FALSE;
return GST_BUFFER_POOL_CLASS (gst_omx_buffer_pool_parent_class)->stop (bpool);
}
static const gchar **
gst_omx_buffer_pool_get_options (GstBufferPool * bpool)
{
static const gchar *raw_video_options[] =
{ GST_BUFFER_POOL_OPTION_VIDEO_META, NULL };
static const gchar *options[] = { NULL };
GstOMXBufferPool *pool = GST_OMX_BUFFER_POOL (bpool);
GST_OBJECT_LOCK (pool);
if (pool->port && pool->port->port_def.eDomain == OMX_PortDomainVideo
&& pool->port->port_def.format.video.eCompressionFormat ==
OMX_VIDEO_CodingUnused) {
GST_OBJECT_UNLOCK (pool);
return raw_video_options;
}
GST_OBJECT_UNLOCK (pool);
return options;
}
static gboolean
gst_omx_buffer_pool_set_config (GstBufferPool * bpool, GstStructure * config)
{
GstOMXBufferPool *pool = GST_OMX_BUFFER_POOL (bpool);
GstCaps *caps;
GST_OBJECT_LOCK (pool);
if (!gst_buffer_pool_config_get_params (config, &caps, NULL, NULL, NULL))
goto wrong_config;
if (caps == NULL)
goto no_caps;
if (pool->port && pool->port->port_def.eDomain == OMX_PortDomainVideo
&& pool->port->port_def.format.video.eCompressionFormat ==
OMX_VIDEO_CodingUnused) {
GstVideoInfo info;
/* now parse the caps from the config */
if (!gst_video_info_from_caps (&info, caps))
goto wrong_video_caps;
/* enable metadata based on config of the pool */
pool->add_videometa =
gst_buffer_pool_config_has_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_META);
pool->video_info = info;
}
if (pool->caps)
gst_caps_unref (pool->caps);
pool->caps = gst_caps_ref (caps);
GST_OBJECT_UNLOCK (pool);
return GST_BUFFER_POOL_CLASS (gst_omx_buffer_pool_parent_class)->set_config
(bpool, config);
/* ERRORS */
wrong_config:
{
GST_OBJECT_UNLOCK (pool);
GST_WARNING_OBJECT (pool, "invalid config");
return FALSE;
}
no_caps:
{
GST_OBJECT_UNLOCK (pool);
GST_WARNING_OBJECT (pool, "no caps in config");
return FALSE;
}
wrong_video_caps:
{
GST_OBJECT_UNLOCK (pool);
GST_WARNING_OBJECT (pool,
"failed getting geometry from caps %" GST_PTR_FORMAT, caps);
return FALSE;
}
}
static GstFlowReturn
gst_omx_buffer_pool_alloc_buffer (GstBufferPool * bpool,
GstBuffer ** buffer, GstBufferPoolAcquireParams * params)
{
GstOMXBufferPool *pool = GST_OMX_BUFFER_POOL (bpool);
GstBuffer *buf;
GstOMXBuffer *omx_buf;
g_return_val_if_fail (pool->allocating, GST_FLOW_ERROR);
omx_buf = g_ptr_array_index (pool->port->buffers, pool->current_buffer_index);
g_return_val_if_fail (omx_buf != NULL, GST_FLOW_ERROR);
if (pool->other_pool) {
guint i, n;
buf = g_ptr_array_index (pool->buffers, pool->current_buffer_index);
g_assert (pool->other_pool == buf->pool);
gst_object_replace ((GstObject **) & buf->pool, NULL);
n = gst_buffer_n_memory (buf);
for (i = 0; i < n; i++) {
GstMemory *mem = gst_buffer_peek_memory (buf, i);
/* FIXME: We don't allow sharing because we need to know
* when the memory becomes unused and can only then put
* it back to the pool. Which is done in the pool's release
* function
*/
GST_MINI_OBJECT_FLAG_SET (mem, GST_MEMORY_FLAG_NO_SHARE);
}
if (pool->add_videometa) {
GstVideoMeta *meta;
meta = gst_buffer_get_video_meta (buf);
if (!meta) {
gst_buffer_add_video_meta (buf, GST_VIDEO_FRAME_FLAG_NONE,
GST_VIDEO_INFO_FORMAT (&pool->video_info),
GST_VIDEO_INFO_WIDTH (&pool->video_info),
GST_VIDEO_INFO_HEIGHT (&pool->video_info));
}
}
} else {
GstMemory *mem;
mem = gst_omx_memory_allocator_alloc (pool->allocator, 0, omx_buf);
buf = gst_buffer_new ();
gst_buffer_append_memory (buf, mem);
g_ptr_array_add (pool->buffers, buf);
if (pool->add_videometa) {
gsize offset[4] = { 0, };
gint stride[4] = { 0, };
switch (pool->video_info.finfo->format) {
case GST_VIDEO_FORMAT_I420:
offset[0] = 0;
stride[0] = pool->port->port_def.format.video.nStride;
offset[1] =
stride[0] * pool->port->port_def.format.video.nSliceHeight;
stride[1] = pool->port->port_def.format.video.nStride / 2;
offset[2] =
offset[1] +
stride[1] * (pool->port->port_def.format.video.nSliceHeight / 2);
stride[2] = pool->port->port_def.format.video.nStride / 2;
break;
case GST_VIDEO_FORMAT_NV12:
offset[0] = 0;
stride[0] = pool->port->port_def.format.video.nStride;
offset[1] =
stride[0] * pool->port->port_def.format.video.nSliceHeight;
stride[1] = pool->port->port_def.format.video.nStride;
break;
default:
g_assert_not_reached ();
break;
}
gst_buffer_add_video_meta_full (buf, GST_VIDEO_FRAME_FLAG_NONE,
GST_VIDEO_INFO_FORMAT (&pool->video_info),
GST_VIDEO_INFO_WIDTH (&pool->video_info),
GST_VIDEO_INFO_HEIGHT (&pool->video_info),
GST_VIDEO_INFO_N_PLANES (&pool->video_info), offset, stride);
}
}
gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (buf),
gst_omx_buffer_data_quark, omx_buf, NULL);
*buffer = buf;
pool->current_buffer_index++;
return GST_FLOW_OK;
}
static void
gst_omx_buffer_pool_free_buffer (GstBufferPool * bpool, GstBuffer * buffer)
{
GstOMXBufferPool *pool = GST_OMX_BUFFER_POOL (bpool);
/* If the buffers belong to another pool, restore them now */
GST_OBJECT_LOCK (pool);
if (pool->other_pool) {
gst_object_replace ((GstObject **) & buffer->pool,
(GstObject *) pool->other_pool);
}
GST_OBJECT_UNLOCK (pool);
gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (buffer),
gst_omx_buffer_data_quark, NULL, NULL);
GST_BUFFER_POOL_CLASS (gst_omx_buffer_pool_parent_class)->free_buffer (bpool,
buffer);
}
static GstFlowReturn
gst_omx_buffer_pool_acquire_buffer (GstBufferPool * bpool,
GstBuffer ** buffer, GstBufferPoolAcquireParams * params)
{
GstFlowReturn ret;
GstOMXBufferPool *pool = GST_OMX_BUFFER_POOL (bpool);
if (pool->port->port_def.eDir == OMX_DirOutput) {
GstBuffer *buf;
g_return_val_if_fail (pool->current_buffer_index != -1, GST_FLOW_ERROR);
buf = g_ptr_array_index (pool->buffers, pool->current_buffer_index);
g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
*buffer = buf;
ret = GST_FLOW_OK;
/* If it's our own memory we have to set the sizes */
if (!pool->other_pool) {
GstMemory *mem = gst_buffer_peek_memory (*buffer, 0);
g_assert (mem
&& g_strcmp0 (mem->allocator->mem_type, GST_OMX_MEMORY_TYPE) == 0);
mem->size = ((GstOMXMemory *) mem)->buf->omx_buf->nFilledLen;
mem->offset = ((GstOMXMemory *) mem)->buf->omx_buf->nOffset;
}
} else {
/* Acquire any buffer that is available to be filled by upstream */
ret =
GST_BUFFER_POOL_CLASS (gst_omx_buffer_pool_parent_class)->acquire_buffer
(bpool, buffer, params);
}
return ret;
}
static void
gst_omx_buffer_pool_release_buffer (GstBufferPool * bpool, GstBuffer * buffer)
{
GstOMXBufferPool *pool = GST_OMX_BUFFER_POOL (bpool);
OMX_ERRORTYPE err;
GstOMXBuffer *omx_buf;
g_assert (pool->component && pool->port);
if (!pool->allocating && !pool->deactivated) {
omx_buf =
gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (buffer),
gst_omx_buffer_data_quark);
if (pool->port->port_def.eDir == OMX_DirOutput && !omx_buf->used) {
/* Release back to the port, can be filled again */
err = gst_omx_port_release_buffer (pool->port, omx_buf);
if (err != OMX_ErrorNone) {
GST_ELEMENT_ERROR (pool->element, LIBRARY, SETTINGS, (NULL),
("Failed to relase output buffer to component: %s (0x%08x)",
gst_omx_error_to_string (err), err));
}
} else if (!omx_buf->used) {
/* TODO: Implement.
*
* If not used (i.e. was not passed to the component) this should do
* the same as EmptyBufferDone.
* If it is used (i.e. was passed to the component) this should do
* nothing until EmptyBufferDone.
*
* EmptyBufferDone should release the buffer to the pool so it can
* be allocated again
*
* Needs something to call back here in EmptyBufferDone, like keeping
* a ref on the buffer in GstOMXBuffer until EmptyBufferDone... which
* would ensure that the buffer is always unused when this is called.
*/
g_assert_not_reached ();
GST_BUFFER_POOL_CLASS (gst_omx_buffer_pool_parent_class)->release_buffer
(bpool, buffer);
}
}
}
static void
gst_omx_buffer_pool_finalize (GObject * object)
{
GstOMXBufferPool *pool = GST_OMX_BUFFER_POOL (object);
if (pool->element)
gst_object_unref (pool->element);
pool->element = NULL;
if (pool->buffers)
g_ptr_array_unref (pool->buffers);
pool->buffers = NULL;
if (pool->other_pool)
gst_object_unref (pool->other_pool);
pool->other_pool = NULL;
if (pool->allocator)
gst_object_unref (pool->allocator);
pool->allocator = NULL;
if (pool->caps)
gst_caps_unref (pool->caps);
pool->caps = NULL;
G_OBJECT_CLASS (gst_omx_buffer_pool_parent_class)->finalize (object);
}
static void
gst_omx_buffer_pool_class_init (GstOMXBufferPoolClass * klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstBufferPoolClass *gstbufferpool_class = (GstBufferPoolClass *) klass;
gst_omx_buffer_data_quark = g_quark_from_static_string ("GstOMXBufferData");
gobject_class->finalize = gst_omx_buffer_pool_finalize;
gstbufferpool_class->start = gst_omx_buffer_pool_start;
gstbufferpool_class->stop = gst_omx_buffer_pool_stop;
gstbufferpool_class->get_options = gst_omx_buffer_pool_get_options;
gstbufferpool_class->set_config = gst_omx_buffer_pool_set_config;
gstbufferpool_class->alloc_buffer = gst_omx_buffer_pool_alloc_buffer;
gstbufferpool_class->free_buffer = gst_omx_buffer_pool_free_buffer;
gstbufferpool_class->acquire_buffer = gst_omx_buffer_pool_acquire_buffer;
gstbufferpool_class->release_buffer = gst_omx_buffer_pool_release_buffer;
}
static void
gst_omx_buffer_pool_init (GstOMXBufferPool * pool)
{
pool->buffers = g_ptr_array_new ();
pool->allocator = g_object_new (gst_omx_memory_allocator_get_type (), NULL);
}
GstBufferPool *
gst_omx_buffer_pool_new (GstElement * element, GstOMXComponent * component,
GstOMXPort * port)
{
GstOMXBufferPool *pool;
pool = g_object_new (gst_omx_buffer_pool_get_type (), NULL);
pool->element = gst_object_ref (element);
pool->component = component;
pool->port = port;
return GST_BUFFER_POOL (pool);
}