gstreamer/omx/gstomxbufferpool.c
Guillaume Desmottes 136714c6ed omxvideodec: add dmabuf support for output
The zynqultrascaleplus OMX implementation has a custom extension
allowing decoders to output dmabuf and so avoid buffers copy between OMX
and GStreamer.

Make use of this extension when built on the zynqultrascaleplus. The
buffer pool code should be re-usable for other platforms as well.

https://bugzilla.gnome.org/show_bug.cgi?id=784847
2017-07-18 23:46:46 +01:00

684 lines
20 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"
#include <gst/allocators/gstdmabuf.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;
gint align;
/* 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;
/* GStreamer uses a bitmask for the alignment while
* OMX uses the alignment itself. So we have to convert
* here */
align = buf->port->port_def.nBufferAlignment;
if (align > 0)
align -= 1;
if (((align + 1) & align) != 0) {
GST_WARNING ("Invalid alignment that is not a power of two: %u",
(guint) buf->port->port_def.nBufferAlignment);
align = 0;
}
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, align, 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;
#define DEBUG_INIT \
GST_DEBUG_CATEGORY_INIT (gst_omx_buffer_pool_debug_category, "omxbufferpool", 0, \
"debug category for gst-omx buffer pool base class");
G_DEFINE_TYPE_WITH_CODE (GstOMXBufferPool, gst_omx_buffer_pool,
GST_TYPE_BUFFER_POOL, DEBUG_INIT);
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));
}
}
pool->need_copy = FALSE;
} else {
GstMemory *mem;
const guint nstride = pool->port->port_def.format.video.nStride;
const guint nslice = pool->port->port_def.format.video.nSliceHeight;
gsize offset[GST_VIDEO_MAX_PLANES] = { 0, };
gint stride[GST_VIDEO_MAX_PLANES] = { nstride, 0, };
if (pool->output_mode == GST_OMX_BUFFER_MODE_DMABUF) {
gint fd;
GstMapInfo map;
fd = GPOINTER_TO_INT (omx_buf->omx_buf->pBuffer);
mem =
gst_dmabuf_allocator_alloc (pool->allocator, fd,
omx_buf->omx_buf->nAllocLen);
if (!gst_caps_features_contains (gst_caps_get_features (pool->caps, 0),
GST_CAPS_FEATURE_MEMORY_DMABUF)) {
/* Check if the memory is actually mappable */
if (!gst_memory_map (mem, &map, GST_MAP_READWRITE)) {
GST_ERROR_OBJECT (pool,
"dmabuf memory is not mappable but caps does not have the 'memory:DMABuf' feature");
gst_memory_unref (mem);
return GST_FLOW_ERROR;
}
gst_memory_unmap (mem, &map);
}
} else {
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);
switch (GST_VIDEO_INFO_FORMAT (&pool->video_info)) {
case GST_VIDEO_FORMAT_ABGR:
case GST_VIDEO_FORMAT_ARGB:
case GST_VIDEO_FORMAT_RGB16:
case GST_VIDEO_FORMAT_BGR16:
case GST_VIDEO_FORMAT_YUY2:
case GST_VIDEO_FORMAT_UYVY:
case GST_VIDEO_FORMAT_YVYU:
case GST_VIDEO_FORMAT_GRAY8:
break;
case GST_VIDEO_FORMAT_I420:
stride[1] = nstride / 2;
offset[1] = offset[0] + stride[0] * nslice;
stride[2] = nstride / 2;
offset[2] = offset[1] + (stride[1] * nslice / 2);
break;
case GST_VIDEO_FORMAT_NV12:
case GST_VIDEO_FORMAT_NV16:
stride[1] = nstride;
offset[1] = offset[0] + stride[0] * nslice;
break;
default:
g_assert_not_reached ();
break;
}
if (pool->add_videometa) {
pool->need_copy = FALSE;
} else {
GstVideoInfo info;
gboolean need_copy = FALSE;
gint i;
gst_video_info_init (&info);
gst_video_info_set_format (&info,
GST_VIDEO_INFO_FORMAT (&pool->video_info),
GST_VIDEO_INFO_WIDTH (&pool->video_info),
GST_VIDEO_INFO_HEIGHT (&pool->video_info));
for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&pool->video_info); i++) {
if (info.stride[i] != stride[i] || info.offset[i] != offset[i]) {
need_copy = TRUE;
break;
}
}
pool->need_copy = need_copy;
}
if (pool->need_copy || pool->add_videometa) {
/* We always add the videometa. It's the job of the user
* to copy the buffer if pool->need_copy is TRUE
*/
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);
GstOMXBuffer *omx_buf;
if (pool->output_mode == GST_OMX_BUFFER_MODE_DMABUF) {
omx_buf =
gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (buf),
gst_omx_buffer_data_quark);
} else {
g_assert (mem
&& g_strcmp0 (mem->allocator->mem_type, GST_OMX_MEMORY_TYPE) == 0);
/* We already have a pointer to the GstOMXBuffer, no need to retrieve it
* from the qdata */
omx_buf = ((GstOMXMemory *) mem)->buf;
}
mem->size = omx_buf->omx_buf->nFilledLen;
mem->offset = omx_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 ();
}
GstBufferPool *
gst_omx_buffer_pool_new (GstElement * element, GstOMXComponent * component,
GstOMXPort * port, GstOMXBufferMode output_mode)
{
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;
pool->output_mode = output_mode;
switch (output_mode) {
case GST_OMX_BUFFER_MODE_DMABUF:
pool->allocator = gst_dmabuf_allocator_new ();
break;
case GST_OMX_BUFFER_MODE_SYSTEM_MEMORY:
pool->allocator =
g_object_new (gst_omx_memory_allocator_get_type (), NULL);
break;
default:
g_assert_not_reached ();
}
return GST_BUFFER_POOL (pool);
}