From bc1e73e8c84540318ee34a465d13c754d32c8dba Mon Sep 17 00:00:00 2001 From: =?utf8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 28 Jun 2011 08:51:23 +0200 Subject: [PATCH] omx: Add initial version of OpenMAX framework, video decoder base class and MPEG4 video decoder This currently hardcodes a lot of stuff but works at least. Also adds a generic framework for handling OpenMAX cores, components and ports. --- omx/Makefile.am | 33 ++ omx/gstomx.c | 1232 +++++++++++++++++++++++++++++++++++++++++++++ omx/gstomx.h | 151 ++++++ omx/gstomxmpeg4videodec.c | 139 +++++ omx/gstomxmpeg4videodec.h | 60 +++ omx/gstomxvideodec.c | 786 +++++++++++++++++++++++++++++ omx/gstomxvideodec.h | 77 +++ 7 files changed, 2478 insertions(+) create mode 100644 omx/gstomx.h create mode 100644 omx/gstomxmpeg4videodec.c create mode 100644 omx/gstomxmpeg4videodec.h create mode 100644 omx/gstomxvideodec.c create mode 100644 omx/gstomxvideodec.h diff --git a/omx/Makefile.am b/omx/Makefile.am index e69de29..1e43413 100644 --- a/omx/Makefile.am +++ b/omx/Makefile.am @@ -0,0 +1,33 @@ +plugin_LTLIBRARIES = libgstomx.la + +libgstomx_la_SOURCES = \ + gstomx.c \ + gstomxvideodec.c \ + gstomxmpeg4videodec.c \ + gstbasevideocodec.c \ + gstbasevideodecoder.c \ + gstbasevideoencoder.c \ + gstbasevideoutils.c + +noinst_HEADERS = \ + gstomxvideodec.h \ + gstomxmpeg4videodec.h \ + gstbasevideocodec.h \ + gstbasevideodecoder.h \ + gstbasevideoencoder.h + +libgstomx_la_CFLAGS = \ + -DGST_USE_UNSTABLE_API=1 \ + -I$(srcdir)/openmax \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) +libgstomx_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) \ + -lgstvideo-@GST_MAJORMINOR@ \ + $(GST_BASE_LIBS) \ + $(GST_LIBS) +libgstomx_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +EXTRA_DIST = openmax + diff --git a/omx/gstomx.c b/omx/gstomx.c index e69de29..fbf55ab 100644 --- a/omx/gstomx.c +++ b/omx/gstomx.c @@ -0,0 +1,1232 @@ +/* + * Copyright (C) 2011, Hewlett-Packard Development Company, L.P. + * Author: Sebastian Dröge , Collabora Ltd. + * + * 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 +#include + +#include "gstomx.h" +#include "gstomxmpeg4videodec.h" + +GST_DEBUG_CATEGORY (gstomx_debug); +#define GST_CAT_DEFAULT gstomx_debug + +G_LOCK_DEFINE_STATIC (core_handles); +static GHashTable *core_handles; + +GstOMXCore * +gst_omx_core_acquire (const gchar * filename) +{ + GstOMXCore *core; + + G_LOCK (core_handles); + if (!core_handles) + core_handles = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + core = g_hash_table_lookup (core_handles, filename); + if (!core) { + core = g_slice_new0 (GstOMXCore); + core->lock = g_mutex_new (); + core->user_count = 0; + g_hash_table_insert (core_handles, g_strdup (filename), core); + + core->module = g_module_open (filename, G_MODULE_BIND_LAZY); + if (!core->module) + goto load_failed; + + if (!g_module_symbol (core->module, "OMX_Init", (gpointer *) & core->init)) + goto symbol_error; + if (!g_module_symbol (core->module, "OMX_Deinit", + (gpointer *) & core->deinit)) + goto symbol_error; + if (!g_module_symbol (core->module, "OMX_GetHandle", + (gpointer *) & core->get_handle)) + goto symbol_error; + if (!g_module_symbol (core->module, "OMX_FreeHandle", + (gpointer *) & core->free_handle)) + goto symbol_error; + + GST_DEBUG ("Successfully loaded core '%s'", filename); + } + + g_mutex_lock (core->lock); + core->user_count++; + if (core->user_count == 1) { + OMX_ERRORTYPE err; + + err = core->init (); + if (err != OMX_ErrorNone) { + GST_ERROR ("Failed to initialize core '%s': 0x%08x", filename, err); + g_mutex_unlock (core->lock); + goto error; + } + + GST_DEBUG ("Successfully initialized core '%s'", filename); + } + + g_mutex_unlock (core->lock); + G_UNLOCK (core_handles); + + return core; + +load_failed: + { + GST_ERROR ("Failed to load module '%s': %s", filename, g_module_error ()); + goto error; + } +symbol_error: + { + GST_ERROR ("Failed to locate required OpenMAX symbol in '%s': %s", filename, + g_module_error ()); + g_module_close (core->module); + core->module = NULL; + goto error; + } +error: + { + g_hash_table_remove (core_handles, filename); + g_mutex_free (core->lock); + g_slice_free (GstOMXCore, core); + + G_UNLOCK (core_handles); + + return NULL; + } +} + +void +gst_omx_core_release (GstOMXCore * core) +{ + g_return_if_fail (core != NULL); + + G_LOCK (core_handles); + + g_mutex_lock (core->lock); + + GST_DEBUG ("Releasing core %p", core); + + core->user_count--; + if (core->user_count == 0) { + GST_DEBUG ("Deinit core %p", core); + core->deinit (); + } + + g_mutex_unlock (core->lock); + + G_UNLOCK (core_handles); +} + +static OMX_ERRORTYPE +EventHandler (OMX_HANDLETYPE hComponent, OMX_PTR pAppData, OMX_EVENTTYPE eEvent, + OMX_U32 nData1, OMX_U32 nData2, OMX_PTR pEventData) +{ + GstOMXComponent *comp = (GstOMXComponent *) pAppData; + + switch (eEvent) { + case OMX_EventCmdComplete: + { + OMX_COMMANDTYPE cmd = (OMX_COMMANDTYPE) nData1; + + GST_DEBUG_OBJECT (comp->parent, "Command %d complete", cmd); + + switch (cmd) { + case OMX_CommandStateSet:{ + /* Notify everything that waits for + * a state change to be finished */ + GST_DEBUG_OBJECT (comp->parent, "State change to %d finished", + nData2); + g_mutex_lock (comp->state_lock); + comp->state = (OMX_STATETYPE) nData2; + if (comp->state == comp->pending_state) + comp->pending_state = OMX_StateInvalid; + g_cond_broadcast (comp->state_cond); + g_mutex_unlock (comp->state_lock); + break; + } + case OMX_CommandFlush:{ + GstOMXPort *port = NULL; + OMX_U32 index = nData2; + + port = gst_omx_component_get_port (comp, index); + if (!port) + break; + + GST_DEBUG_OBJECT (comp->parent, "Port %u flushed", port->index); + + /* Now notify gst_omx_port_set_flushing() + * that the port is really flushed now and + * we can continue + */ + g_mutex_lock (port->port_lock); + /* If this is ever called when the port + * was not set to flushing something went + * wrong but it happens for some reason. + */ + if (port->flushing) { + port->flushed = TRUE; + g_cond_broadcast (port->port_cond); + } else { + g_debug ("Port %u is not flushing\n", (guint32) port->index); + } + g_mutex_unlock (port->port_lock); + break; + } + case OMX_CommandPortEnable: + case OMX_CommandPortDisable:{ + GstOMXPort *port = NULL; + OMX_U32 index = nData2; + + port = gst_omx_component_get_port (comp, index); + if (!port) + break; + + GST_DEBUG_OBJECT (comp->parent, "Port %u %s", port->index, + (cmd == OMX_CommandPortEnable ? "enabled" : "disabled")); + + g_mutex_lock (port->port_lock); + g_cond_broadcast (port->port_cond); + g_mutex_unlock (port->port_lock); + + break; + } + default: + break; + } + break; + } + case OMX_EventError: + { + gint i, n; + OMX_ERRORTYPE err = nData1; + + if (err == OMX_ErrorNone) + break; + + GST_ERROR_OBJECT (comp->parent, "Got error %d\n", err); + + /* Error events are always fatal, notify all + * condition variables that something went + * wrong + */ + g_mutex_lock (comp->state_lock); + comp->last_error = err; + g_cond_broadcast (comp->state_cond); + g_mutex_unlock (comp->state_lock); + + /* Now notify all ports, no locking needed + * here because the ports are allocated in the + * very beginning and never change again until + * component destruction. + */ + n = comp->ports->len; + for (i = 0; i < n; i++) { + GstOMXPort *tmp = g_ptr_array_index (comp->ports, i); + + g_mutex_lock (tmp->port_lock); + g_cond_broadcast (tmp->port_cond); + g_mutex_unlock (tmp->port_lock); + } + break; + } + case OMX_EventPortSettingsChanged: + { + OMX_U32 index; + GstOMXPort *port = NULL; + + /* FIXME XXX: WTF? Bellagio passes + * the port index as *second* parameter + * instead of first... + */ + index = nData2; + + port = gst_omx_component_get_port (comp, index); + if (!port) + break; + + GST_DEBUG_OBJECT (comp->parent, "Settings of port %u changed", index); + + g_mutex_lock (port->port_lock); + port->settings_changed = TRUE; + g_cond_broadcast (port->port_cond); + g_mutex_unlock (port->port_lock); + + /* FIXME XXX: Bellagio only sends the event for the + * input port even if the output port settings change + * too... + */ + { + gint i, n; + + n = comp->ports->len; + for (i = 0; i < n; i++) { + port = g_ptr_array_index (comp->ports, i); + + /* Don't notify the same port twice */ + if (port->index == index) + continue; + + g_mutex_lock (port->port_lock); + port->settings_changed = TRUE; + g_cond_broadcast (port->port_cond); + g_mutex_unlock (port->port_lock); + } + } + + break; + } + case OMX_EventPortFormatDetected: + case OMX_EventBufferFlag: + default: + break; + } + + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE +EmptyBufferDone (OMX_HANDLETYPE hComponent, OMX_PTR pAppData, + OMX_BUFFERHEADERTYPE * pBuffer) +{ + GstOMXBuffer *buf = pBuffer->pAppPrivate; + + g_assert (buf->omx_buf == pBuffer); + + /* Input buffer is empty again and can + * be used to contain new input */ + g_mutex_lock (buf->port->port_lock); + GST_DEBUG_OBJECT (buf->port->comp->parent, "Port %u emptied buffer %p", + buf->port->index, buf); + buf->used = FALSE; + g_queue_push_tail (buf->port->pending_buffers, buf); + g_cond_broadcast (buf->port->port_cond); + g_mutex_unlock (buf->port->port_lock); + return OMX_ErrorNone; +} + +static OMX_ERRORTYPE +FillBufferDone (OMX_HANDLETYPE hComponent, OMX_PTR pAppData, + OMX_BUFFERHEADERTYPE * pBuffer) +{ + GstOMXBuffer *buf = pBuffer->pAppPrivate; + + g_assert (buf->omx_buf == pBuffer); + + /* Output buffer contains output now or + * the port was flushed */ + g_mutex_lock (buf->port->port_lock); + GST_DEBUG_OBJECT (buf->port->comp->parent, "Port %u filled buffer %p", + buf->port->index, buf); + buf->used = FALSE; + g_queue_push_tail (buf->port->pending_buffers, buf); + g_cond_broadcast (buf->port->port_cond); + g_mutex_unlock (buf->port->port_lock); + return OMX_ErrorNone; +} + +static OMX_CALLBACKTYPE callbacks = + { EventHandler, EmptyBufferDone, FillBufferDone }; + +GstOMXComponent * +gst_omx_component_new (GstObject * parent, const gchar * core_name, + const gchar * component_name) +{ + OMX_ERRORTYPE err; + GstOMXCore *core; + GstOMXComponent *comp; + + core = gst_omx_core_acquire (core_name); + if (!core) + return NULL; + + comp = g_slice_new0 (GstOMXComponent); + comp->core = core; + + err = + core->get_handle (&comp->handle, (OMX_STRING) component_name, comp, + &callbacks); + if (err != OMX_ErrorNone) { + GST_ERROR_OBJECT (parent, + "Failed to get component handle '%s' from core '%s': 0x%08x", + component_name, core_name, err); + gst_omx_core_release (core); + g_slice_free (GstOMXComponent, comp); + return NULL; + } + GST_DEBUG_OBJECT (parent, + "Successfully got component handle %p (%s) from core '%s'", comp->handle, + component_name, core_name); + comp->parent = gst_object_ref (parent); + + comp->ports = g_ptr_array_new (); + + comp->state_lock = g_mutex_new (); + comp->state_cond = g_cond_new (); + comp->pending_state = OMX_StateInvalid; + comp->last_error = OMX_ErrorNone; + + OMX_GetState (comp->handle, &comp->state); + + return comp; +} + +void +gst_omx_component_free (GstOMXComponent * comp) +{ + gint i, n; + + g_return_if_fail (comp != NULL); + + GST_DEBUG_OBJECT (comp->parent, "Unloading component %p", comp); + + if (comp->ports) { + n = comp->ports->len; + for (i = 0; i < n; i++) { + GstOMXPort *port = g_ptr_array_index (comp->ports, i); + + g_assert (!port->buffers || port->buffers->len == 0); + + g_mutex_free (port->port_lock); + g_cond_free (port->port_cond); + g_queue_free (port->pending_buffers); + + g_slice_free (GstOMXPort, port); + } + g_ptr_array_unref (comp->ports); + } + + g_cond_free (comp->state_cond); + g_mutex_free (comp->state_lock); + + comp->core->free_handle (comp->handle); + gst_omx_core_release (comp->core); + gst_object_unref (comp->parent); +} + +OMX_ERRORTYPE +gst_omx_component_set_state (GstOMXComponent * comp, OMX_STATETYPE state) +{ + OMX_STATETYPE old_state; + OMX_ERRORTYPE err = OMX_ErrorNone; + + g_return_val_if_fail (comp != NULL, OMX_ErrorUndefined); + + g_mutex_lock (comp->state_lock); + old_state = comp->state; + GST_DEBUG_OBJECT (comp->parent, "Setting state from %d to %d", old_state, + state); + if ((err = comp->last_error) != OMX_ErrorNone) + goto done; + if (old_state == state || comp->pending_state == state) + goto done; + + comp->pending_state = state; + err = OMX_SendCommand (comp->handle, OMX_CommandStateSet, state, NULL); + +done: + g_mutex_unlock (comp->state_lock); + + if (err != OMX_ErrorNone) + GST_ERROR_OBJECT (comp->parent, "Error setting state from %d to %d: %d", + old_state, state, err); + return err; +} + +OMX_STATETYPE +gst_omx_component_get_state (GstOMXComponent * comp, GstClockTime timeout) +{ + OMX_STATETYPE ret; + GTimeVal *timeval, abstimeout; + gboolean signalled; + + g_return_val_if_fail (comp != NULL, OMX_StateInvalid); + + GST_DEBUG_OBJECT (comp->parent, "Getting state"); + + g_mutex_lock (comp->state_lock); + ret = comp->state; + if (comp->pending_state == OMX_StateInvalid) + goto done; + + if (comp->last_error != OMX_ErrorNone) { + ret = OMX_StateInvalid; + goto done; + } + + if (timeout != GST_CLOCK_TIME_NONE) { + glong add = timeout / 1000; + + if (add == 0) + goto done; + + g_get_current_time (&abstimeout); + g_time_val_add (&abstimeout, add); + timeval = &abstimeout; + GST_DEBUG_OBJECT (comp->parent, "Waiting for %ld us", add); + } else { + timeval = NULL; + GST_DEBUG_OBJECT (comp->parent, "Waiting for signal"); + } + + do { + signalled = g_cond_timed_wait (comp->state_cond, comp->state_lock, timeval); + } while (signalled && comp->last_error == OMX_ErrorNone + && comp->pending_state != OMX_StateInvalid); + + if (signalled) { + if (comp->last_error != OMX_ErrorNone) { + GST_ERROR_OBJECT (comp->parent, + "Got error while waiting for state change: %d", comp->last_error); + ret = OMX_StateInvalid; + } else if (comp->pending_state == OMX_StateInvalid) { + ret = comp->state; + } else { + ret = OMX_StateInvalid; + g_assert_not_reached (); + } + } else { + ret = OMX_StateInvalid; + comp->state = comp->pending_state = OMX_StateInvalid; + GST_WARNING_OBJECT (comp->parent, "Timeout while waiting for state change"); + } + +done: + g_mutex_unlock (comp->state_lock); + + GST_DEBUG_OBJECT (comp->parent, "Returning state %d", ret); + + return ret; +} + +GstOMXPort * +gst_omx_component_add_port (GstOMXComponent * comp, guint32 index) +{ + gint i, n; + GstOMXPort *port; + OMX_PARAM_PORTDEFINITIONTYPE port_def; + OMX_ERRORTYPE err; + + g_return_val_if_fail (comp != NULL, NULL); + + /* Check if this port exists already */ + n = comp->ports->len; + for (i = 0; i < n; i++) { + port = g_ptr_array_index (comp->ports, i); + g_return_val_if_fail (port->index != index, NULL); + } + + GST_DEBUG_OBJECT (comp->parent, "Adding port %u", index); + + port_def.nSize = sizeof (port_def); + port_def.nVersion.s.nVersionMajor = 1; + port_def.nVersion.s.nVersionMinor = 1; + port_def.nPortIndex = index; + err = OMX_GetParameter (comp->handle, OMX_IndexParamPortDefinition, + &port_def); + if (err != OMX_ErrorNone) { + GST_ERROR_OBJECT (comp->parent, "Failed to add port %u: %d", index, err); + return NULL; + } + + port = g_slice_new0 (GstOMXPort); + port->comp = comp; + port->index = index; + + port->port_def = port_def; + + port->port_lock = g_mutex_new (); + port->port_cond = g_cond_new (); + port->pending_buffers = g_queue_new (); + port->flushing = TRUE; + port->flushed = FALSE; + port->settings_changed = FALSE; + + g_ptr_array_add (comp->ports, port); + + return port; +} + +GstOMXPort * +gst_omx_component_get_port (GstOMXComponent * comp, guint32 index) +{ + gint i, n; + + /* No need for locking here because the + * ports are all added directly after + * creating the component and are removed + * when the component is destroyed. + */ + + n = comp->ports->len; + for (i = 0; i < n; i++) { + GstOMXPort *tmp = g_ptr_array_index (comp->ports, i); + + if (tmp->index == index) + return tmp; + } + return NULL; +} + +OMX_ERRORTYPE +gst_omx_component_get_last_error (GstOMXComponent * comp) +{ + OMX_ERRORTYPE err; + + g_return_val_if_fail (comp != NULL, OMX_ErrorUndefined); + + g_mutex_lock (comp->state_lock); + err = comp->last_error; + g_mutex_unlock (comp->state_lock); + + GST_DEBUG_OBJECT (comp->parent, "Returning last error: %d", err); + + return err; +} + +void +gst_omx_port_get_port_definition (GstOMXPort * port, + OMX_PARAM_PORTDEFINITIONTYPE * port_def) +{ + g_return_if_fail (port != NULL); + + memset (port_def, 0, sizeof (*port_def)); + port_def->nSize = sizeof (*port_def); + port_def->nVersion.s.nVersionMajor = 1; + port_def->nVersion.s.nVersionMinor = 1; + port_def->nPortIndex = port->index; + + OMX_GetParameter (port->comp->handle, OMX_IndexParamPortDefinition, port_def); +} + +gboolean +gst_omx_port_update_port_definition (GstOMXPort * port, + OMX_PARAM_PORTDEFINITIONTYPE * port_def) +{ + OMX_ERRORTYPE err = OMX_ErrorNone; + + g_return_val_if_fail (port != NULL, FALSE); + + g_mutex_lock (port->port_lock); + if (port_def) + err = + OMX_SetParameter (port->comp->handle, OMX_IndexParamPortDefinition, + port_def); + OMX_GetParameter (port->comp->handle, OMX_IndexParamPortDefinition, + &port->port_def); + + GST_DEBUG_OBJECT (port->comp->parent, "Updated port %u definition: %d", + port->index, err); + + g_mutex_unlock (port->port_lock); + + return (err == OMX_ErrorNone); +} + +GstOMXBuffer * +gst_omx_port_acquire_buffer (GstOMXPort * port) +{ + GstOMXBuffer *buf = NULL; + + GST_DEBUG_OBJECT (port->comp->parent, "Acquiring buffer from port %u", + port->index); + + g_mutex_lock (port->port_lock); + if (port->flushing) + goto done; + + /* Check if the component is in an error state */ + g_mutex_lock (port->comp->state_lock); + if (port->comp->last_error != OMX_ErrorNone) { + g_mutex_unlock (port->comp->state_lock); + goto done; + } + g_mutex_unlock (port->comp->state_lock); + + /* Wait until there's something in the queue + * or something else happened that requires + * to return a NULL buffer, e.g. an error + */ + if (g_queue_is_empty (port->pending_buffers)) + g_cond_wait (port->port_cond, port->port_lock); + + /* Check if the component is in an error state */ + g_mutex_lock (port->comp->state_lock); + if (port->comp->last_error != OMX_ErrorNone) { + g_mutex_unlock (port->comp->state_lock); + goto done; + } + g_mutex_unlock (port->comp->state_lock); + + if (!g_queue_is_empty (port->pending_buffers)) + buf = g_queue_pop_head (port->pending_buffers); + +done: + g_mutex_unlock (port->port_lock); + + GST_DEBUG_OBJECT (port->comp->parent, "Acquired buffer %p from port %u", buf, + port->index); + + return buf; +} + +OMX_ERRORTYPE +gst_omx_port_release_buffer (GstOMXPort * port, GstOMXBuffer * buffer) +{ + OMX_ERRORTYPE err = OMX_ErrorNone; + + GST_DEBUG_OBJECT (port->comp->parent, "Releasing buffer %p to port %u", + buffer, port->index); + + g_mutex_lock (port->port_lock); + + if (port->flushing) + goto done; + + buffer->used = TRUE; + if (port->port_def.eDir == OMX_DirInput) { + err = OMX_EmptyThisBuffer (port->comp->handle, buffer->omx_buf); + } else { + err = OMX_FillThisBuffer (port->comp->handle, buffer->omx_buf); + } + +done: + GST_DEBUG_OBJECT (port->comp->parent, "Released buffer %p to port %u: %d", + buffer, port->index, err); + g_mutex_unlock (port->port_lock); + + return err; +} + +OMX_ERRORTYPE +gst_omx_port_set_flushing (GstOMXPort * port, gboolean flush) +{ + OMX_ERRORTYPE err = OMX_ErrorNone; + + g_return_val_if_fail (port != NULL, OMX_ErrorUndefined); + + GST_DEBUG_OBJECT (port->comp->parent, "Setting port %d to %sflushing", + port->index, (flush ? "" : "not ")); + + g_mutex_lock (port->port_lock); + if (! !flush == ! !port->flushing) { + GST_DEBUG_OBJECT (port->comp->parent, "Port %u was %sflushing already", + port->index, (flush ? "" : "not ")); + goto done; + } + + g_mutex_lock (port->comp->state_lock); + if ((port->comp->state != OMX_StateIdle + && port->comp->state != OMX_StateExecuting) + || port->comp->last_error != OMX_ErrorNone) { + + if (port->comp->last_error != OMX_ErrorNone) { + err = port->comp->last_error; + GST_ERROR_OBJECT (port->comp->parent, "Component is in error state: %d", + err); + } else { + GST_ERROR_OBJECT (port->comp->parent, "Component is in wrong state: %d", + port->comp->state); + err = OMX_ErrorUndefined; + } + + g_mutex_unlock (port->comp->state_lock); + goto done; + } + g_mutex_unlock (port->comp->state_lock); + + port->flushing = flush; + if (flush) + g_cond_broadcast (port->port_cond); + + if (flush) { + GTimeVal abstimeout, *timeval; + gboolean signalled; + OMX_ERRORTYPE last_error; + + port->flushed = FALSE; + err = + OMX_SendCommand (port->comp->handle, OMX_CommandFlush, port->index, + NULL); + if (err != OMX_ErrorNone) { + GST_ERROR_OBJECT (port->comp->parent, + "Error sending flush command to port %u: %d", port->index, err); + goto done; + } + + g_get_current_time (&abstimeout); + g_time_val_add (&abstimeout, 5 * 10000000); + timeval = &abstimeout; + GST_DEBUG_OBJECT (port->comp->parent, "Waiting for 5s"); + + /* Retry until timeout or until an error happend or + * until all buffers were released by the component and + * the flush command completed */ + do { + signalled = g_cond_timed_wait (port->port_cond, port->port_lock, timeval); + + g_mutex_lock (port->comp->state_lock); + last_error = port->comp->last_error; + g_mutex_unlock (port->comp->state_lock); + } while (signalled && last_error == OMX_ErrorNone && !port->flushed + && port->buffers->len != g_queue_get_length (port->pending_buffers)); + port->flushed = FALSE; + + GST_DEBUG_OBJECT (port->comp->parent, "Port %d flushed", port->index); + if (last_error != OMX_ErrorNone) { + GST_ERROR_OBJECT (port->comp->parent, + "Got error while flushing port %u: %d", port->index, last_error); + err = last_error; + goto done; + } else if (!signalled) { + GST_ERROR_OBJECT (port->comp->parent, "Timeout while flushing port %u", + port->index); + err = OMX_ErrorTimeout; + goto done; + } + } else { + if (port->port_def.eDir == OMX_DirOutput && port->buffers) { + gint i, n; + + /* Enqueue all buffers for the component to fill */ + n = port->buffers->len; + for (i = 0; i < n; i++) { + GstOMXBuffer *buf = g_ptr_array_index (port->buffers, i); + + g_assert (!buf->used); + + err = OMX_FillThisBuffer (port->comp->handle, buf->omx_buf); + if (err != OMX_ErrorNone) { + GST_ERROR_OBJECT (port->comp->parent, + "Failed to pass buffer %p to port %u: %d", buf, port->index, err); + g_mutex_lock (port->comp->state_lock); + port->comp->last_error = err; + g_mutex_unlock (port->comp->state_lock); + goto done; + } + } + + g_queue_clear (port->pending_buffers); + } + } + +done: + GST_DEBUG_OBJECT (port->comp->parent, "Set port %u to %sflushing: %d", + port->index, (flush ? "" : "not "), err); + g_mutex_unlock (port->port_lock); + + return err; +} + +gboolean +gst_omx_port_is_flushing (GstOMXPort * port) +{ + gboolean flushing; + + g_return_val_if_fail (port != NULL, FALSE); + + g_mutex_lock (port->port_lock); + flushing = port->flushing; + g_mutex_unlock (port->port_lock); + + GST_DEBUG_OBJECT (port->comp->parent, "Port %u is flushing: %d", port->index, + flushing); + + return flushing; +} + +/* Must be called while holding port->lock */ +static OMX_ERRORTYPE +gst_omx_port_allocate_buffers_unlocked (GstOMXPort * port) +{ + OMX_ERRORTYPE err = OMX_ErrorNone; + gint i, n; + + g_assert (!port->buffers || port->buffers->len == 0); + + /* Update the port definition to check if we need more + * buffers after the port configuration was done and to + * update the buffer size + */ + OMX_GetParameter (port->comp->handle, OMX_IndexParamPortDefinition, + &port->port_def); + + /* If the configured, actual number of buffers is less than + * the minimal number of buffers required, use the minimal + * number of buffers + */ + if (port->port_def.nBufferCountActual < port->port_def.nBufferCountMin) { + port->port_def.nBufferCountActual = port->port_def.nBufferCountMin; + OMX_SetParameter (port->comp->handle, OMX_IndexParamPortDefinition, + &port->port_def); + OMX_GetParameter (port->comp->handle, OMX_IndexParamPortDefinition, + &port->port_def); + } + + n = port->port_def.nBufferCountActual; + GST_DEBUG_OBJECT (port->comp->parent, + "Allocating %d buffers of size %u for port %u", n, + port->port_def.nBufferSize, port->index); + + if (!port->buffers) + port->buffers = g_ptr_array_sized_new (n); + + for (i = 0; i < n; i++) { + GstOMXBuffer *buf; + + buf = g_slice_new0 (GstOMXBuffer); + buf->port = port; + buf->used = FALSE; + g_ptr_array_add (port->buffers, buf); + + err = + OMX_AllocateBuffer (port->comp->handle, &buf->omx_buf, port->index, buf, + port->port_def.nBufferSize); + if (err != OMX_ErrorNone) { + GST_ERROR_OBJECT (port->comp->parent, "Failed to allocate buffer: %d", + err); + port->comp->last_error = err; + break; + } + + /* In the beginning all buffers are not owned by the component */ + g_queue_push_tail (port->pending_buffers, buf); + } + g_cond_broadcast (port->port_cond); + + GST_DEBUG_OBJECT (port->comp->parent, "Allocated buffers for port %u: %d", + port->index, err); + + return err; +} + +OMX_ERRORTYPE +gst_omx_port_allocate_buffers (GstOMXPort * port) +{ + OMX_ERRORTYPE err; + + g_return_val_if_fail (port != NULL, OMX_ErrorUndefined); + + g_mutex_lock (port->port_lock); + err = gst_omx_port_allocate_buffers_unlocked (port); + g_mutex_unlock (port->port_lock); + + return err; +} + +/* Must be called while holding port->lock */ +static OMX_ERRORTYPE +gst_omx_port_deallocate_buffers_unlocked (GstOMXPort * port) +{ + OMX_ERRORTYPE err = OMX_ErrorNone; + gint i, n; + + GST_DEBUG_OBJECT (port->comp->parent, "Deallocating buffers of port %u", + port->index); + + if (!port->buffers) { + GST_DEBUG_OBJECT (port->comp->parent, "No buffers allocated for port %u", + port->index); + goto done; + } + + /* We only allow deallocation of buffers after they + * were all released from the port, either by flushing + * the port or by disabling it. + */ + g_assert (g_queue_get_length (port->pending_buffers) == port->buffers->len); + + n = port->buffers->len; + + for (i = 0; i < n; i++) { + GstOMXBuffer *buf = g_ptr_array_index (port->buffers, i); + OMX_ERRORTYPE tmp = OMX_ErrorNone; + + g_assert (!buf->used); + + /* omx_buf can be NULL if allocation failed earlier + * and we're just shutting down + * + * errors do not cause exiting this loop because we want + * to deallocate as much as possible. + */ + if (buf->omx_buf) { + tmp = OMX_FreeBuffer (port->comp->handle, port->index, buf->omx_buf); + if (tmp != OMX_ErrorNone && err == OMX_ErrorNone) + err = tmp; + + } + g_slice_free (GstOMXBuffer, buf); + } + + g_queue_clear (port->pending_buffers); + g_ptr_array_unref (port->buffers); + port->buffers = NULL; +done: + GST_DEBUG_OBJECT (port->comp->parent, "Deallocated buffers of port %u: %d", + port->index, err); + + return err; +} + +OMX_ERRORTYPE +gst_omx_port_deallocate_buffers (GstOMXPort * port) +{ + OMX_ERRORTYPE err; + + g_return_val_if_fail (port != NULL, OMX_ErrorUndefined); + + g_mutex_lock (port->port_lock); + err = gst_omx_port_deallocate_buffers_unlocked (port); + g_mutex_unlock (port->port_lock); + + return err; +} + +/* Must be called while holding port->lock */ +static OMX_ERRORTYPE +gst_omx_port_set_enabled_unlocked (GstOMXPort * port, gboolean enabled) +{ + OMX_ERRORTYPE err = OMX_ErrorNone; + GTimeVal abstimeout, *timeval; + gboolean signalled; + OMX_ERRORTYPE last_error; + + GST_DEBUG_OBJECT (port->comp->parent, "Setting port %u to %s", port->index, + (enabled ? "enabled" : "disabled")); + + /* Check if the port is already enabled/disabled first */ + OMX_GetParameter (port->comp->handle, OMX_IndexParamPortDefinition, + &port->port_def); + if (! !port->port_def.bEnabled == ! !enabled) + goto done; + + if (enabled) + err = + OMX_SendCommand (port->comp->handle, OMX_CommandPortEnable, port->index, + NULL); + else + err = + OMX_SendCommand (port->comp->handle, OMX_CommandPortDisable, + port->index, NULL); + if (err != OMX_ErrorNone) { + GST_ERROR_OBJECT (port->comp->parent, + "Failed to send enable/disable command to port %u: %d", port->index, + err); + goto done; + } + + g_get_current_time (&abstimeout); + g_time_val_add (&abstimeout, 5 * 10000000); + timeval = &abstimeout; + GST_DEBUG_OBJECT (port->comp->parent, "Waiting for 5s"); + + /* FIXME XXX: The spec says that bEnabled should be set *immediately* + * but bellagio sets bEnabled after all buffers are allocated/deallocated + */ + + /* First wait until all buffers are released by the port */ + signalled = TRUE; + last_error = OMX_ErrorNone; + while (signalled && last_error == OMX_ErrorNone && (port->buffers + && port->buffers->len != + g_queue_get_length (port->pending_buffers))) { + signalled = g_cond_timed_wait (port->port_cond, port->port_lock, timeval); + g_mutex_lock (port->comp->state_lock); + last_error = port->comp->last_error; + g_mutex_unlock (port->comp->state_lock); + } + + if (last_error != OMX_ErrorNone) { + err = last_error; + GST_ERROR_OBJECT (port->comp->parent, + "Got error while waiting for port %u to release all buffers: %d", + port->index, err); + } else if (!signalled) { + GST_ERROR_OBJECT (port->comp->parent, + "Timeout waiting for port %u to release all buffers", port->index); + err = OMX_ErrorTimeout; + } + + /* Allocate/deallocate all buffers for the port to finish + * the enable/disable command */ + if (enabled) { + /* If allocation fails this component can't really be used anymore */ + if ((err = gst_omx_port_allocate_buffers_unlocked (port)) != OMX_ErrorNone) { + g_mutex_lock (port->comp->state_lock); + port->comp->last_error = err; + g_cond_broadcast (port->comp->state_cond); + g_mutex_unlock (port->comp->state_lock); + goto done; + } + } else { + /* If deallocation fails this component can't really be used anymore */ + if ((err = + gst_omx_port_deallocate_buffers_unlocked (port)) != OMX_ErrorNone) { + g_mutex_lock (port->comp->state_lock); + port->comp->last_error = err; + g_cond_broadcast (port->comp->state_cond); + g_mutex_unlock (port->comp->state_lock); + goto done; + } + } + + /* And now wait until the enable/disable command is finished */ + signalled = TRUE; + last_error = OMX_ErrorNone; + OMX_GetParameter (port->comp->handle, OMX_IndexParamPortDefinition, + &port->port_def); + while (signalled && last_error == OMX_ErrorNone + && (! !port->port_def.bEnabled != ! !enabled)) { + signalled = g_cond_timed_wait (port->port_cond, port->port_lock, timeval); + g_mutex_lock (port->comp->state_lock); + last_error = port->comp->last_error; + g_mutex_unlock (port->comp->state_lock); + OMX_GetParameter (port->comp->handle, OMX_IndexParamPortDefinition, + &port->port_def); + } + + if (!signalled) { + GST_ERROR_OBJECT (port->comp->parent, + "Timeout waiting for port %u to be enabled/disabled", port->index); + err = OMX_ErrorTimeout; + } else if (last_error != OMX_ErrorNone) { + GST_ERROR_OBJECT (port->comp->parent, + "Got error while waiting for port %u to be enabled/disabled: %d", + port->index, err); + err = last_error; + } + +done: + GST_DEBUG_OBJECT (port->comp->parent, "Port %u is %s%s: %d", port->index, + (err == OMX_ErrorNone ? "" : "not "), + (enabled ? "enabled" : "disabled"), err); + + return err; +} + +OMX_ERRORTYPE +gst_omx_port_set_enabled (GstOMXPort * port, gboolean enabled) +{ + OMX_ERRORTYPE err; + + g_return_val_if_fail (port != NULL, OMX_ErrorUndefined); + + g_mutex_lock (port->port_lock); + err = gst_omx_port_set_enabled_unlocked (port, enabled); + g_mutex_unlock (port->port_lock); + + return err; +} + +gboolean +gst_omx_port_is_enabled (GstOMXPort * port) +{ + gboolean enabled; + + g_return_val_if_fail (port != NULL, FALSE); + + g_mutex_lock (port->port_lock); + OMX_GetParameter (port->comp->handle, OMX_IndexParamPortDefinition, + &port->port_def); + enabled = port->port_def.bEnabled; + g_mutex_unlock (port->port_lock); + + GST_DEBUG_OBJECT (port->comp->parent, "Port %u is enabled: %d", port->index, + enabled); + + return enabled; +} + +gboolean +gst_omx_port_is_settings_changed (GstOMXPort * port) +{ + gboolean settings_changed; + + g_return_val_if_fail (port != NULL, FALSE); + + g_mutex_lock (port->port_lock); + settings_changed = port->settings_changed; + g_mutex_unlock (port->port_lock); + + GST_DEBUG_OBJECT (port->comp->parent, "Port %u has settings-changed: %d", + port->index, settings_changed); + + return settings_changed; +} + +OMX_ERRORTYPE +gst_omx_port_reconfigure (GstOMXPort * port) +{ + OMX_ERRORTYPE err = OMX_ErrorNone; + + g_return_val_if_fail (port != NULL, OMX_ErrorUndefined); + + GST_DEBUG_OBJECT (port->comp->parent, "Reconfiguring port %u", port->index); + + g_mutex_lock (port->port_lock); + + if (!port->settings_changed) + goto done; + + /* Disable and enable the port. This already takes + * care of deallocating and allocating buffers. + */ + err = gst_omx_port_set_enabled_unlocked (port, FALSE); + if (err != OMX_ErrorNone) + goto done; + + err = gst_omx_port_set_enabled_unlocked (port, TRUE); + if (err != OMX_ErrorNone) + goto done; + + port->settings_changed = FALSE; + +done: + GST_DEBUG_OBJECT (port->comp->parent, "Reconfigured port %u: %d", port->index, + err); + + g_mutex_unlock (port->port_lock); + return err; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean ret = FALSE; + + GST_DEBUG_CATEGORY_INIT (gstomx_debug, "omx", 0, "gst-omx"); + + /* TODO: Use configuration file */ + ret |= + gst_element_register (plugin, "omxmpeg4videodec", GST_RANK_PRIMARY, + GST_TYPE_OMX_MPEG4_VIDEO_DEC); + + return ret; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "omx", + "GStreamer OpenMAX Plug-ins", + plugin_init, + PACKAGE_VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/omx/gstomx.h b/omx/gstomx.h new file mode 100644 index 0000000..a8405f5 --- /dev/null +++ b/omx/gstomx.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2011, Hewlett-Packard Development Company, L.P. + * Author: Sebastian Dröge , Collabora Ltd. + * + * 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 + * + */ + +#ifndef __GST_OMX_H__ +#define __GST_OMX_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstOMXCore GstOMXCore; +typedef struct _GstOMXPort GstOMXPort; +typedef enum _GstOMXPortDirection GstOMXPortDirection; +typedef struct _GstOMXComponent GstOMXComponent; +typedef struct _GstOMXBuffer GstOMXBuffer; + +struct _GstOMXCore { + /* Handle to the OpenMAX IL core shared library */ + GModule *module; + + /* Current number of users, transitions from/to 0 + * call init/deinit */ + GMutex *lock; + gint user_count; /* LOCK */ + + /* OpenMAX core library functions, protected with LOCK */ + /* FIXME: OpenMAX spec does not specify that this is required + * but gst-openmax does it */ + OMX_ERRORTYPE (*init) (void); + OMX_ERRORTYPE (*deinit) (void); + OMX_ERRORTYPE (*get_handle) (OMX_HANDLETYPE * handle, + OMX_STRING name, OMX_PTR data, OMX_CALLBACKTYPE * callbacks); + OMX_ERRORTYPE (*free_handle) (OMX_HANDLETYPE handle); +}; + +struct _GstOMXPort { + GstOMXComponent *comp; + OMX_U32 index; + + /* Protects port_def, buffers, pending_buffers, + * settings_changed, flushing and flushed. + * + * Signalled if pending_buffers gets a + * new buffer or flushing/flushed is set + * to TRUE or an error happens. Always + * check comp->last_error after being + * signalled! + * + * Note: flushed==TRUE implies flushing==TRUE! + * + * Note: This lock must always be taken before + * the component's state lock if both are needed! + */ + GMutex *port_lock; + GCond *port_cond; + OMX_PARAM_PORTDEFINITIONTYPE port_def; + GPtrArray *buffers; + GQueue *pending_buffers; + /* If TRUE we need to get the new caps + * of this port */ + gboolean settings_changed; + gboolean flushing; + gboolean flushed; /* TRUE after OMX_CommandFlush was done */ +}; + +struct _GstOMXComponent { + GstObject *parent; + OMX_HANDLETYPE handle; + GstOMXCore *core; + + GPtrArray *ports; + + /* Protecting state, pending_state and last_error + * Signalled if one of them changes + */ + GMutex *state_lock; + GCond *state_cond; + OMX_STATETYPE state; + /* OMX_StateInvalid if no pending state */ + OMX_STATETYPE pending_state; + /* OMX_ErrorNone usually, if different nothing will work */ + OMX_ERRORTYPE last_error; +}; + +struct _GstOMXBuffer { + GstOMXPort *port; + OMX_BUFFERHEADERTYPE *omx_buf; + + /* TRUE if the buffer is used by the port, i.e. + * between {Empty,Fill}ThisBuffer and the callback + */ + gboolean used; +}; + +GstOMXCore * gst_omx_core_acquire (const gchar * filename); +void gst_omx_core_release (GstOMXCore * core); + + +GstOMXComponent * gst_omx_component_new (GstObject *parent, const gchar * core_name, const gchar * component_name); +void gst_omx_component_free (GstOMXComponent * comp); + +OMX_ERRORTYPE gst_omx_component_set_state (GstOMXComponent * comp, OMX_STATETYPE state); +OMX_STATETYPE gst_omx_component_get_state (GstOMXComponent * comp, GstClockTime timeout); + +OMX_ERRORTYPE gst_omx_component_get_last_error (GstOMXComponent * comp); + +GstOMXPort * gst_omx_component_add_port (GstOMXComponent * comp, guint32 index); +GstOMXPort * gst_omx_component_get_port (GstOMXComponent * comp, guint32 index); + + +void gst_omx_port_get_port_definition (GstOMXPort * port, OMX_PARAM_PORTDEFINITIONTYPE * port_def); +gboolean gst_omx_port_update_port_definition (GstOMXPort *port, OMX_PARAM_PORTDEFINITIONTYPE *port_definition); + +GstOMXBuffer * gst_omx_port_acquire_buffer (GstOMXPort *port); +OMX_ERRORTYPE gst_omx_port_release_buffer (GstOMXPort *port, GstOMXBuffer *buffer); + +OMX_ERRORTYPE gst_omx_port_set_flushing (GstOMXPort *port, gboolean flush); +gboolean gst_omx_port_is_flushing (GstOMXPort *port); + +OMX_ERRORTYPE gst_omx_port_allocate_buffers (GstOMXPort *port); +OMX_ERRORTYPE gst_omx_port_deallocate_buffers (GstOMXPort *port); + +OMX_ERRORTYPE gst_omx_port_reconfigure (GstOMXPort * port); + +OMX_ERRORTYPE gst_omx_port_set_enabled (GstOMXPort * port, gboolean enabled); +gboolean gst_omx_port_is_enabled (GstOMXPort * port); + +gboolean gst_omx_port_is_settings_changed (GstOMXPort * port); + +G_END_DECLS + +#endif /* __GST_OMX_H__ */ diff --git a/omx/gstomxmpeg4videodec.c b/omx/gstomxmpeg4videodec.c new file mode 100644 index 0000000..55d992a --- /dev/null +++ b/omx/gstomxmpeg4videodec.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2011, Hewlett-Packard Development Company, L.P. + * Author: Sebastian Dröge , Collabora Ltd. + * + * 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 + +#include "gstomxmpeg4videodec.h" + +GST_DEBUG_CATEGORY_STATIC (gst_omx_mpeg4_video_dec_debug_category); +#define GST_CAT_DEFAULT gst_omx_mpeg4_video_dec_debug_category + +/* prototypes */ +static void gst_omx_mpeg4_video_dec_finalize (GObject * object); +static gboolean gst_omx_mpeg4_video_dec_is_format_change (GstOMXVideoDec * dec, + GstOMXPort * port, GstVideoState * state); +static gboolean gst_omx_mpeg4_video_dec_set_format (GstOMXVideoDec * dec, + GstOMXPort * port, GstVideoState * state); + +enum +{ + PROP_0 +}; + +/* class initialization */ + +#define DEBUG_INIT(bla) \ + GST_DEBUG_CATEGORY_INIT (gst_omx_mpeg4_video_dec_debug_category, "omxvideodec", 0, \ + "debug category for gst-omx video decoder base class"); + +GST_BOILERPLATE_FULL (GstOMXMPEG4VideoDec, gst_omx_mpeg4_video_dec, + GstOMXVideoDec, GST_TYPE_OMX_VIDEO_DEC, DEBUG_INIT); + +static GstStaticPadTemplate gst_omx_mpeg4_video_dec_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/mpeg, " + "mpegversion=(int) 4, " + "systemstream=(boolean) false, " + "parsed=(boolean) true, " + "width=(int) [ 16, 4096 ], " "height=(int) [ 16, 4096 ]") + ); + +static GstStaticPadTemplate gst_omx_mpeg4_video_dec_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420")) + ); + +static void +gst_omx_mpeg4_video_dec_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_omx_mpeg4_video_dec_src_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_omx_mpeg4_video_dec_sink_template)); + + gst_element_class_set_details_simple (element_class, + "OpenMAX MPEG4 Video Decoder", + "Codec/Decoder/Video", + "Decode MPEG4 video streams", + "Sebastian Dröge "); +} + +static void +gst_omx_mpeg4_video_dec_class_init (GstOMXMPEG4VideoDecClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstOMXVideoDecClass *videodec_class = GST_OMX_VIDEO_DEC_CLASS (klass); + + gobject_class->finalize = gst_omx_mpeg4_video_dec_finalize; + + /* TODO: Make this configurable */ + videodec_class->core_name = "/usr/local/lib/libomxil-bellagio.so.0"; + videodec_class->component_name = "OMX.st.video_decoder.mpeg4"; + + videodec_class->is_format_change = + GST_DEBUG_FUNCPTR (gst_omx_mpeg4_video_dec_is_format_change); + videodec_class->set_format = + GST_DEBUG_FUNCPTR (gst_omx_mpeg4_video_dec_set_format); +} + +static void +gst_omx_mpeg4_video_dec_init (GstOMXMPEG4VideoDec * self, + GstOMXMPEG4VideoDecClass * klass) +{ +} + +static void +gst_omx_mpeg4_video_dec_finalize (GObject * object) +{ + /* GstOMXMPEG4VideoDec *self = GST_OMX_MPEG4_VIDEO_DEC (object); */ + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_omx_mpeg4_video_dec_is_format_change (GstOMXVideoDec * dec, + GstOMXPort * port, GstVideoState * state) +{ + return FALSE; +} + +static gboolean +gst_omx_mpeg4_video_dec_set_format (GstOMXVideoDec * dec, GstOMXPort * port, + GstVideoState * state) +{ + gboolean ret; + OMX_PARAM_PORTDEFINITIONTYPE port_def; + + gst_omx_port_get_port_definition (port, &port_def); + port_def.format.video.eCompressionFormat = OMX_VIDEO_CodingMPEG4; + ret = gst_omx_port_update_port_definition (port, &port_def); + + return ret; +} diff --git a/omx/gstomxmpeg4videodec.h b/omx/gstomxmpeg4videodec.h new file mode 100644 index 0000000..73a68d5 --- /dev/null +++ b/omx/gstomxmpeg4videodec.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011, Hewlett-Packard Development Company, L.P. + * Author: Sebastian Dröge , Collabora Ltd. + * + * 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 + * + */ + +#ifndef __GST_OMX_MPEG4_VIDEO_DEC_H__ +#define __GST_OMX_MPEG4_VIDEO_DEC_H__ + +#include +#include "gstomxvideodec.h" + +G_BEGIN_DECLS + +#define GST_TYPE_OMX_MPEG4_VIDEO_DEC \ + (gst_omx_mpeg4_video_dec_get_type()) +#define GST_OMX_MPEG4_VIDEO_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OMX_MPEG4_VIDEO_DEC,GstOMXMPEG4VideoDec)) +#define GST_OMX_MPEG4_VIDEO_DEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OMX_MPEG4_VIDEO_DEC,GstOMXMPEG4VideoDecClass)) +#define GST_OMX_MPEG4_VIDEO_DEC_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_OMX_MPEG4_VIDEO_DEC,GstOMXMPEG4VideoDecClass)) +#define GST_IS_OMX_MPEG4_VIDEO_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OMX_MPEG4_VIDEO_DEC)) +#define GST_IS_OMX_MPEG4_VIDEO_DEC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OMX_MPEG4_VIDEO_DEC)) + +typedef struct _GstOMXMPEG4VideoDec GstOMXMPEG4VideoDec; +typedef struct _GstOMXMPEG4VideoDecClass GstOMXMPEG4VideoDecClass; + +struct _GstOMXMPEG4VideoDec +{ + GstOMXVideoDec parent; +}; + +struct _GstOMXMPEG4VideoDecClass +{ + GstOMXVideoDecClass parent_class; +}; + +GType gst_omx_mpeg4_video_dec_get_type (void); + +G_END_DECLS + +#endif /* __GST_OMX_MPEG4_VIDEO_DEC_H__ */ + diff --git a/omx/gstomxvideodec.c b/omx/gstomxvideodec.c new file mode 100644 index 0000000..8007182 --- /dev/null +++ b/omx/gstomxvideodec.c @@ -0,0 +1,786 @@ +/* + * Copyright (C) 2011, Hewlett-Packard Development Company, L.P. + * Author: Sebastian Dröge , Collabora Ltd. + * + * 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 +#include + +#include "gstomxvideodec.h" + +GST_DEBUG_CATEGORY_STATIC (gst_omx_video_dec_debug_category); +#define GST_CAT_DEFAULT gst_omx_video_dec_debug_category + +typedef struct _BufferIdentification BufferIdentification; +struct _BufferIdentification +{ + guint64 timestamp; +}; + +/* prototypes */ +static void gst_omx_video_dec_finalize (GObject * object); + +static GstStateChangeReturn +gst_omx_video_dec_change_state (GstElement * element, + GstStateChange transition); + +static gboolean gst_omx_video_dec_start (GstBaseVideoDecoder * decoder); +static gboolean gst_omx_video_dec_stop (GstBaseVideoDecoder * decoder); +static gboolean gst_omx_video_dec_set_format (GstBaseVideoDecoder * decoder, + GstVideoState * state); +static gboolean gst_omx_video_dec_reset (GstBaseVideoDecoder * decoder); +static GstFlowReturn gst_omx_video_dec_parse_data (GstBaseVideoDecoder * + decoder, gboolean at_eos); +static GstFlowReturn gst_omx_video_dec_handle_frame (GstBaseVideoDecoder * + decoder, GstVideoFrame * frame); +static GstFlowReturn gst_omx_video_dec_finish (GstBaseVideoDecoder * decoder); + +enum +{ + PROP_0 +}; + +/* class initialization */ + +#define DEBUG_INIT(bla) \ + GST_DEBUG_CATEGORY_INIT (gst_omx_video_dec_debug_category, "omxvideodec", 0, \ + "debug category for gst-omx video decoder base class"); + +GST_BOILERPLATE_FULL (GstOMXVideoDec, gst_omx_video_dec, GstBaseVideoDecoder, + GST_TYPE_BASE_VIDEO_DECODER, DEBUG_INIT); + +static void +gst_omx_video_dec_base_init (gpointer g_class) +{ +} + +static void +gst_omx_video_dec_class_init (GstOMXVideoDecClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstBaseVideoDecoderClass *base_video_decoder_class = + GST_BASE_VIDEO_DECODER_CLASS (klass); + + gobject_class->finalize = gst_omx_video_dec_finalize; + + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_omx_video_dec_change_state); + + base_video_decoder_class->start = GST_DEBUG_FUNCPTR (gst_omx_video_dec_start); + base_video_decoder_class->stop = GST_DEBUG_FUNCPTR (gst_omx_video_dec_stop); + base_video_decoder_class->reset = GST_DEBUG_FUNCPTR (gst_omx_video_dec_reset); + base_video_decoder_class->set_format = + GST_DEBUG_FUNCPTR (gst_omx_video_dec_set_format); + base_video_decoder_class->parse_data = + GST_DEBUG_FUNCPTR (gst_omx_video_dec_parse_data); + base_video_decoder_class->handle_frame = + GST_DEBUG_FUNCPTR (gst_omx_video_dec_handle_frame); + base_video_decoder_class->finish = + GST_DEBUG_FUNCPTR (gst_omx_video_dec_finish); +} + +static void +gst_omx_video_dec_init (GstOMXVideoDec * self, GstOMXVideoDecClass * klass) +{ + GST_BASE_VIDEO_DECODER (self)->packetized = TRUE; +} + +static gboolean +gst_omx_video_dec_open (GstOMXVideoDec * self) +{ + GstOMXVideoDecClass *klass = GST_OMX_VIDEO_DEC_GET_CLASS (self); + + self->component = + gst_omx_component_new (GST_OBJECT_CAST (self), klass->core_name, + klass->component_name); + + if (!self->component) + return FALSE; + + if (gst_omx_component_get_state (self->component, + GST_CLOCK_TIME_NONE) != OMX_StateLoaded) + return FALSE; + + /* FIXME: Always 0 == input, 1 == output? Make configurable? Let subclass decide? */ + self->in_port = gst_omx_component_add_port (self->component, 0); + self->out_port = gst_omx_component_add_port (self->component, 1); + + if (!self->in_port || !self->out_port) + return FALSE; + + return TRUE; +} + +static gboolean +gst_omx_video_dec_close (GstOMXVideoDec * self) +{ + OMX_STATETYPE state; + + gst_omx_component_set_state (self->component, OMX_StateLoaded); + gst_omx_port_deallocate_buffers (self->in_port); + gst_omx_port_deallocate_buffers (self->out_port); + state = gst_omx_component_get_state (self->component, 5 * GST_SECOND); + + self->in_port = NULL; + self->out_port = NULL; + if (self->component) + gst_omx_component_free (self->component); + self->component = NULL; + + return (state == OMX_StateLoaded); +} + +static void +gst_omx_video_dec_finalize (GObject * object) +{ + /* GstOMXVideoDec *self = GST_OMX_VIDEO_DEC (object); */ + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GstStateChangeReturn +gst_omx_video_dec_change_state (GstElement * element, GstStateChange transition) +{ + GstOMXVideoDec *self; + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + g_return_val_if_fail (GST_IS_OMX_VIDEO_DEC (element), + GST_STATE_CHANGE_FAILURE); + self = GST_OMX_VIDEO_DEC (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_omx_video_dec_open (self)) + ret = GST_STATE_CHANGE_FAILURE; + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_omx_port_set_flushing (self->out_port, FALSE); + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_omx_port_set_flushing (self->out_port, TRUE); + break; + default: + break; + } + + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_NULL: + if (!gst_omx_video_dec_close (self)) + ret = GST_STATE_CHANGE_FAILURE; + break; + default: + break; + } + + return ret; +} + +#define MAX_FRAME_DIST_TICKS (5 * OMX_TICKS_PER_SECOND) +#define MAX_FRAME_DIST_FRAMES (100) + +static GstVideoFrame * +_find_nearest_frame (GstOMXVideoDec * self, GstOMXBuffer * buf) +{ + GList *l, *best_l = NULL; + GstVideoFrame *best = NULL; + guint64 best_timestamp = 0; + guint64 best_diff = G_MAXUINT64; + BufferIdentification *best_id = NULL; + + GST_OBJECT_LOCK (self); + for (l = self->pending_frames; l; l = l->next) { + GstVideoFrame *tmp = l->data; + BufferIdentification *id = tmp->coder_hook; + guint64 timestamp, diff; + + /* This happens for frames that were just added but + * which were not passed to the component yet. Ignore + * them here! + */ + if (!id) + continue; + + timestamp = id->timestamp; + + if (timestamp > buf->omx_buf->nTimeStamp) + diff = timestamp - buf->omx_buf->nTimeStamp; + else + diff = buf->omx_buf->nTimeStamp - timestamp; + + if (best == NULL || diff < best_diff) { + best = tmp; + best_timestamp = timestamp; + best_diff = diff; + best_l = l; + best_id = id; + + /* For frames without timestamp we simply take the first frame */ + if ((buf->omx_buf->nTimeStamp == 0 && timestamp == 0) || diff == 0) + break; + } + } + + if (best_id) { + for (l = self->pending_frames; l && l != best_l;) { + GstVideoFrame *tmp = l->data; + BufferIdentification *id = tmp->coder_hook; + guint64 diff_ticks, diff_frames; + + if (id->timestamp > best_timestamp) + break; + + if (id->timestamp == 0 || best_timestamp == 0) + diff_ticks = 0; + else + diff_ticks = best_timestamp - id->timestamp; + diff_frames = best->system_frame_number - tmp->system_frame_number; + + if (diff_ticks > MAX_FRAME_DIST_TICKS + || diff_frames > MAX_FRAME_DIST_FRAMES) { + g_warning ("Too old frame, bug in decoder -- please file a bug"); + gst_base_video_decoder_finish_frame (GST_BASE_VIDEO_DECODER (self), + tmp); + self->pending_frames = g_list_delete_link (self->pending_frames, l); + l = self->pending_frames; + } else { + l = l->next; + } + } + } + + if (best_l) + self->pending_frames = g_list_delete_link (self->pending_frames, best_l); + GST_OBJECT_UNLOCK (self); + + return best; +} + +static void +gst_omx_video_dec_loop (GstOMXVideoDec * self) +{ + GstOMXPort *port = self->out_port; + GstOMXBuffer *buf = NULL; + GstVideoFrame *frame; + GstFlowReturn flow_ret = GST_FLOW_OK; + + buf = gst_omx_port_acquire_buffer (port); + if (!buf) { + if (gst_omx_component_get_last_error (self->component) != OMX_ErrorNone) { + goto component_error; + } else if (!gst_omx_port_is_settings_changed (self->out_port)) { + goto flushing; + } + } + + if (!GST_PAD_CAPS (GST_BASE_VIDEO_CODEC_SRC_PAD (self)) + || gst_omx_port_is_settings_changed (self->out_port)) { + GstVideoState *state = &GST_BASE_VIDEO_CODEC (self)->state; + OMX_PARAM_PORTDEFINITIONTYPE port_def; + + gst_omx_port_get_port_definition (port, &port_def); + g_assert (port_def.format.video.eCompressionFormat == + OMX_VIDEO_CodingUnused); + + switch (port_def.format.video.eColorFormat) { + case OMX_COLOR_FormatYUV420Planar: + state->format = GST_VIDEO_FORMAT_I420; + break; + default: + g_assert_not_reached (); + break; + } + + state->width = port_def.format.video.nFrameWidth; + state->height = port_def.format.video.nFrameHeight; + /* FIXME XXX: Bellagio does not set this to something useful... */ + /* gst_util_double_to_fraction (port_def.format.video.xFramerate / ((gdouble) 0xffff), &state->fps_n, &state->fps_d); */ + gst_base_video_decoder_set_src_caps (GST_BASE_VIDEO_DECODER (self)); + + if (gst_omx_port_is_settings_changed (self->out_port)) { + if (gst_omx_port_reconfigure (self->out_port) != OMX_ErrorNone) + goto reconfigure_error; + } + + /* Get a new buffer */ + if (!buf) + return; + } + + GST_DEBUG_OBJECT (self, "Handling buffer: 0x%08x %lu", buf->omx_buf->nFlags, + buf->omx_buf->nTimeStamp); + + frame = _find_nearest_frame (self, buf); + if (!frame) { + GST_ERROR_OBJECT (self, "No corresponding frame found"); + } else if (buf->omx_buf->nFilledLen > 0) { + if (gst_base_video_decoder_alloc_src_frame (GST_BASE_VIDEO_DECODER (self), + frame) == GST_FLOW_OK) { + memcpy (GST_BUFFER_DATA (frame->src_buffer), + buf->omx_buf->pBuffer + buf->omx_buf->nOffset, + buf->omx_buf->nFilledLen); + } + g_slice_free (BufferIdentification, frame->coder_hook); + flow_ret = + gst_base_video_decoder_finish_frame (GST_BASE_VIDEO_DECODER (self), + frame); + } else if (frame != NULL) { + g_slice_free (BufferIdentification, frame->coder_hook); + flow_ret = + gst_base_video_decoder_finish_frame (GST_BASE_VIDEO_DECODER (self), + frame); + } + + if (flow_ret == GST_FLOW_OK && (buf->omx_buf->nFlags & OMX_BUFFERFLAG_EOS)) + flow_ret = GST_FLOW_UNEXPECTED; + + gst_omx_port_release_buffer (port, buf); + + if (flow_ret != GST_FLOW_OK) + goto flow_error; + + return; + +component_error: + { + GST_ELEMENT_ERROR (self, LIBRARY, FAILED, (NULL), + ("OpenMAX component in error state %d", + gst_omx_component_get_last_error (self->component))); + gst_pad_push_event (GST_BASE_VIDEO_CODEC_SRC_PAD (self), + gst_event_new_eos ()); + gst_pad_pause_task (GST_BASE_VIDEO_CODEC_SRC_PAD (self)); + return; + } +flushing: + { + GST_DEBUG_OBJECT (self, "Flushing -- stopping task"); + gst_pad_pause_task (GST_BASE_VIDEO_CODEC_SRC_PAD (self)); + return; + } +flow_error: + { + if (flow_ret == GST_FLOW_UNEXPECTED) { + GST_DEBUG_OBJECT (self, "EOS"); + + gst_pad_push_event (GST_BASE_VIDEO_CODEC_SRC_PAD (self), + gst_event_new_eos ()); + gst_pad_pause_task (GST_BASE_VIDEO_CODEC_SRC_PAD (self)); + } else if (flow_ret == GST_FLOW_NOT_LINKED + || flow_ret < GST_FLOW_UNEXPECTED) { + GST_ELEMENT_ERROR (self, STREAM, FAILED, ("Internal data stream error."), + ("stream stopped, reason %s", gst_flow_get_name (flow_ret))); + + gst_pad_push_event (GST_BASE_VIDEO_CODEC_SRC_PAD (self), + gst_event_new_eos ()); + gst_pad_pause_task (GST_BASE_VIDEO_CODEC_SRC_PAD (self)); + } + return; + } +reconfigure_error: + { + GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, (NULL), + ("Unable to reconfigure output port")); + gst_pad_push_event (GST_BASE_VIDEO_CODEC_SRC_PAD (self), + gst_event_new_eos ()); + gst_pad_pause_task (GST_BASE_VIDEO_CODEC_SRC_PAD (self)); + return; + } +} + +static gboolean +gst_omx_video_dec_start (GstBaseVideoDecoder * decoder) +{ + GstOMXVideoDec *self; + gboolean ret; + + self = GST_OMX_VIDEO_DEC (decoder); + + ret = + gst_pad_start_task (GST_BASE_VIDEO_CODEC_SRC_PAD (self), + (GstTaskFunction) gst_omx_video_dec_loop, self); + + return ret; +} + +static gboolean +gst_omx_video_dec_stop (GstBaseVideoDecoder * decoder) +{ + GstOMXVideoDec *self; + gboolean ret; + + self = GST_OMX_VIDEO_DEC (decoder); + + ret = gst_pad_stop_task (GST_BASE_VIDEO_CODEC_SRC_PAD (decoder)); + + gst_omx_component_set_state (self->component, OMX_StateIdle); + + gst_omx_port_set_flushing (self->in_port, TRUE); + gst_omx_port_set_flushing (self->out_port, TRUE); + + gst_omx_component_get_state (self->component, 5 * GST_SECOND); + + g_list_free (self->pending_frames); + self->pending_frames = NULL; + + gst_buffer_replace (&self->codec_data, NULL); + + return ret; +} + +static gboolean +gst_omx_video_dec_set_format (GstBaseVideoDecoder * decoder, + GstVideoState * state) +{ + GstOMXVideoDec *self; + GstOMXVideoDecClass *klass; + gboolean is_format_change = FALSE; + gboolean needs_disable = FALSE; + OMX_PARAM_PORTDEFINITIONTYPE port_def; + + self = GST_OMX_VIDEO_DEC (decoder); + klass = GST_OMX_VIDEO_DEC_GET_CLASS (decoder); + + /* FIXME: If called again later, properly set states and reinitialize + * only possible in Loaded state or if port is disabled => + * delay if state>loaded into the port-disabled callback */ + + gst_omx_port_get_port_definition (self->in_port, &port_def); + + /* Check if the caps change is a real format change or if only irrelevant + * parts of the caps have changed or nothing at all. + */ + is_format_change |= port_def.format.video.nFrameWidth != state->width; + is_format_change |= port_def.format.video.nFrameHeight != state->height; + is_format_change |= (port_def.format.video.xFramerate == 0 + && state->fps_n != 0) + || (port_def.format.video.xFramerate != + (state->fps_n << 16) / (state->fps_d)); + is_format_change |= (self->codec_data != state->codec_data); + if (klass->is_format_change) + is_format_change |= klass->is_format_change (self, self->in_port, state); + + needs_disable = + gst_omx_component_get_state (self->component, + GST_CLOCK_TIME_NONE) != OMX_StateLoaded; + /* If the component is not in Loaded state and a real format change happens + * we have to disable the port and re-allocate all buffers. If no real + * format change happened we can just exit here. + */ + if (needs_disable && !is_format_change) { + GST_DEBUG_OBJECT (self, + "Already running and caps did not change the format"); + return TRUE; + } + if (needs_disable && is_format_change) { + if (gst_omx_port_set_enabled (self->in_port, FALSE) != OMX_ErrorNone) + return FALSE; + } + + port_def.format.video.nFrameWidth = state->width - 100; + port_def.format.video.nFrameHeight = state->height; + if (state->fps_n == 0) + port_def.format.video.xFramerate = 0; + else + port_def.format.video.xFramerate = (state->fps_n << 16) / (state->fps_d); + + if (!gst_omx_port_update_port_definition (self->in_port, &port_def)) + return FALSE; + if (!gst_omx_port_update_port_definition (self->out_port, NULL)) + return FALSE; + + if (klass->set_format) { + if (!klass->set_format (self, self->in_port, state)) { + GST_ERROR_OBJECT (self, "Subclass failed to set the new format"); + return FALSE; + } + } + + gst_buffer_replace (&self->codec_data, state->codec_data); + + if (needs_disable) { + if (gst_omx_port_set_enabled (self->in_port, TRUE) != OMX_ErrorNone) + return FALSE; + } else { + if (gst_omx_component_set_state (self->component, + OMX_StateIdle) != OMX_ErrorNone) + return FALSE; + + /* Need to allocate buffers to reach Idle state */ + if (gst_omx_port_allocate_buffers (self->in_port) != OMX_ErrorNone) + return FALSE; + if (gst_omx_port_allocate_buffers (self->out_port) != OMX_ErrorNone) + return FALSE; + + if (gst_omx_component_get_state (self->component, + GST_CLOCK_TIME_NONE) != OMX_StateIdle) + return FALSE; + + if (gst_omx_component_set_state (self->component, + OMX_StateExecuting) != OMX_ErrorNone) + return FALSE; + } + + /* Unset flushing to allow ports to accept data again */ + gst_omx_port_set_flushing (self->in_port, FALSE); + gst_omx_port_set_flushing (self->out_port, FALSE); + + if (gst_omx_component_get_last_error (self->component) != OMX_ErrorNone) { + GST_ERROR_OBJECT (self, "Component in error state: %d", + gst_omx_component_get_last_error (self->component)); + return FALSE; + } + + /* Start the srcpad loop again */ + gst_pad_start_task (GST_BASE_VIDEO_CODEC_SRC_PAD (self), + (GstTaskFunction) gst_omx_video_dec_loop, decoder); + + return (gst_omx_component_get_state (self->component, + GST_CLOCK_TIME_NONE) == OMX_StateExecuting); +} + +static gboolean +gst_omx_video_dec_reset (GstBaseVideoDecoder * decoder) +{ + GstOMXVideoDec *self; + + self = GST_OMX_VIDEO_DEC (decoder); + + GST_DEBUG_OBJECT (self, "Resetting decoder"); + + gst_omx_port_set_flushing (self->in_port, TRUE); + gst_omx_port_set_flushing (self->out_port, TRUE); + + /* Wait until the srcpad loop is finished */ + GST_PAD_STREAM_LOCK (GST_BASE_VIDEO_CODEC_SRC_PAD (self)); + GST_PAD_STREAM_UNLOCK (GST_BASE_VIDEO_CODEC_SRC_PAD (self)); + + g_list_free (self->pending_frames); + self->pending_frames = NULL; + + gst_omx_port_set_flushing (self->in_port, FALSE); + gst_omx_port_set_flushing (self->out_port, FALSE); + + /* Start the srcpad loop again */ + gst_pad_start_task (GST_BASE_VIDEO_CODEC_SRC_PAD (self), + (GstTaskFunction) gst_omx_video_dec_loop, decoder); + + return TRUE; +} + +static GstFlowReturn +gst_omx_video_dec_parse_data (GstBaseVideoDecoder * decoder, gboolean at_eos) +{ + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_omx_video_dec_handle_frame (GstBaseVideoDecoder * decoder, + GstVideoFrame * frame) +{ + GstOMXVideoDec *self; + GstOMXBuffer *buf; + GstBuffer *codec_data = NULL; + + self = GST_OMX_VIDEO_DEC (decoder); + + GST_DEBUG_OBJECT (self, "Handling frame"); + + if (gst_omx_port_is_flushing (self->in_port)) + goto flushing; + if (gst_omx_component_get_last_error (self->component) != OMX_ErrorNone) + goto component_error; + + if (gst_omx_port_is_settings_changed (self->in_port)) { + if (gst_omx_port_reconfigure (self->in_port) != OMX_ErrorNone) + goto reconfigure_error; + } + + if (self->codec_data) { + codec_data = self->codec_data; + + retry_codec_data: + buf = gst_omx_port_acquire_buffer (self->in_port); + if (!buf) { + if (gst_omx_component_get_last_error (self->component) != OMX_ErrorNone) { + goto component_error; + } else if (gst_omx_port_is_settings_changed (self->in_port)) { + if (gst_omx_port_reconfigure (self->in_port) != OMX_ErrorNone) + goto reconfigure_error; + goto retry_codec_data; + } else { + goto flushing; + } + } + + if (buf->omx_buf->nAllocLen < GST_BUFFER_SIZE (codec_data)) { + gst_omx_port_release_buffer (self->in_port, buf); + goto too_large_codec_data; + } + + buf->omx_buf->nFlags |= OMX_BUFFERFLAG_CODECCONFIG; + buf->omx_buf->nFilledLen = GST_BUFFER_SIZE (codec_data); + memcpy (buf->omx_buf->pBuffer + buf->omx_buf->nOffset, + GST_BUFFER_DATA (codec_data), GST_BUFFER_SIZE (codec_data)); + + gst_omx_port_release_buffer (self->in_port, buf); + self->codec_data = NULL; + } + + { + guint offset = 0; + GstClockTime timestamp, duration, timestamp_offset = 0; + + GST_OBJECT_LOCK (self); + self->pending_frames = g_list_append (self->pending_frames, frame); + GST_OBJECT_UNLOCK (self); + + timestamp = frame->presentation_timestamp; + duration = frame->presentation_duration; + + while (offset < GST_BUFFER_SIZE (frame->sink_buffer)) { + buf = gst_omx_port_acquire_buffer (self->in_port); + + if (!buf) { + if (gst_omx_component_get_last_error (self->component) != OMX_ErrorNone) { + goto component_error; + } else if (gst_omx_port_is_settings_changed (self->in_port)) { + if (gst_omx_port_reconfigure (self->in_port) != OMX_ErrorNone) + goto reconfigure_error; + continue; + } else { + goto flushing; + } + } + + /* Copy the buffer content in chunks of size as requested + * by the port */ + buf->omx_buf->nFilledLen = + MIN (GST_BUFFER_SIZE (frame->sink_buffer) - offset, + buf->omx_buf->nAllocLen - buf->omx_buf->nOffset); + memcpy (buf->omx_buf->pBuffer + buf->omx_buf->nOffset, + GST_BUFFER_DATA (frame->sink_buffer) + offset, + buf->omx_buf->nFilledLen); + + /* Interpolate timestamps if we're passing the buffer + * in multiple chunks */ + if (offset != 0 && duration != GST_CLOCK_TIME_NONE) { + timestamp_offset = + gst_util_uint64_scale (offset, duration, + GST_BUFFER_SIZE (frame->sink_buffer)); + } + + if (timestamp != GST_CLOCK_TIME_NONE) { + buf->omx_buf->nTimeStamp = + gst_util_uint64_scale (timestamp + timestamp_offset, + OMX_TICKS_PER_SECOND, GST_SECOND); + } + if (duration != GST_CLOCK_TIME_NONE) { + buf->omx_buf->nTickCount = + gst_util_uint64_scale (buf->omx_buf->nFilledLen, duration, + GST_BUFFER_SIZE (frame->sink_buffer)); + } + + if (offset == 0) { + BufferIdentification *id = g_slice_new0 (BufferIdentification); + + id->timestamp = buf->omx_buf->nTimeStamp; + frame->coder_hook = id; + } + + /* TODO: Set flags + * - OMX_BUFFERFLAG_DECODEONLY for buffers that are outside + * the segment + * - OMX_BUFFERFLAG_SYNCFRAME for non-delta frames + * - OMX_BUFFERFLAG_ENDOFFRAME for parsed input + */ + + offset += buf->omx_buf->nFilledLen; + gst_omx_port_release_buffer (self->in_port, buf); + } + } + + return GST_FLOW_OK; + +too_large_codec_data: + { + GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL), + ("codec_data larger than supported by OpenMAX port (%u > %u)", + GST_BUFFER_SIZE (codec_data), self->in_port->port_def.nBufferSize)); + return GST_FLOW_ERROR; + } + +component_error: + { + GST_ELEMENT_ERROR (self, LIBRARY, FAILED, (NULL), + ("OpenMAX component in error state %d", + gst_omx_component_get_last_error (self->component))); + return GST_FLOW_ERROR; + } + +flushing: + { + GST_DEBUG_OBJECT (self, "Flushing -- returning WRONG_STATE"); + return GST_FLOW_WRONG_STATE; + } +reconfigure_error: + { + GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, (NULL), + ("Unable to reconfigure input port")); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_omx_video_dec_finish (GstBaseVideoDecoder * decoder) +{ + GstOMXVideoDec *self; + GstOMXBuffer *buf; + + self = GST_OMX_VIDEO_DEC (decoder); + + GST_DEBUG_OBJECT (self, "Sending EOS to the component"); + + /* Send an EOS buffer to the component and let the base + * class drop the EOS event. We will send it later when + * the EOS buffer arrives on the output port. */ + buf = gst_omx_port_acquire_buffer (self->in_port); + if (buf) { + buf->omx_buf->nFlags |= OMX_BUFFERFLAG_EOS; + gst_omx_port_release_buffer (self->in_port, buf); + } + + return GST_BASE_VIDEO_DECODER_FLOW_DROPPED; +} diff --git a/omx/gstomxvideodec.h b/omx/gstomxvideodec.h new file mode 100644 index 0000000..0ac411c --- /dev/null +++ b/omx/gstomxvideodec.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2011, Hewlett-Packard Development Company, L.P. + * Author: Sebastian Dröge , Collabora Ltd. + * + * 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 + * + */ + +#ifndef __GST_OMX_VIDEO_DEC_H__ +#define __GST_OMX_VIDEO_DEC_H__ + +#include +#include "gstbasevideodecoder.h" + +#include "gstomx.h" + +G_BEGIN_DECLS + +#define GST_TYPE_OMX_VIDEO_DEC \ + (gst_omx_video_dec_get_type()) +#define GST_OMX_VIDEO_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OMX_VIDEO_DEC,GstOMXVideoDec)) +#define GST_OMX_VIDEO_DEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OMX_VIDEO_DEC,GstOMXVideoDecClass)) +#define GST_OMX_VIDEO_DEC_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_OMX_VIDEO_DEC,GstOMXVideoDecClass)) +#define GST_IS_OMX_VIDEO_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OMX_VIDEO_DEC)) +#define GST_IS_OMX_VIDEO_DEC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OMX_VIDEO_DEC)) + +typedef struct _GstOMXVideoDec GstOMXVideoDec; +typedef struct _GstOMXVideoDecClass GstOMXVideoDecClass; + +struct _GstOMXVideoDec +{ + GstBaseVideoDecoder parent; + + /* < protected > */ + GstOMXCore *core; + GstOMXComponent *component; + GstOMXPort *in_port, *out_port; + + /* < private > */ + GList *pending_frames; + + GstBuffer *codec_data; +}; + +struct _GstOMXVideoDecClass +{ + GstBaseVideoDecoderClass parent_class; + + const gchar *core_name; + const gchar *component_name; + + gboolean (*is_format_change) (GstOMXVideoDec * self, GstOMXPort * port, GstVideoState * state); + gboolean (*set_format) (GstOMXVideoDec * self, GstOMXPort * port, GstVideoState * state); +}; + +GType gst_omx_video_dec_get_type (void); + +G_END_DECLS + +#endif /* __GST_OMX_VIDEO_DEC_H__ */ -- 2.7.4