omx: Add initial version of OpenMAX framework, video decoder base class and MPEG4...
authorSebastian Dröge <sebastian.droege@collabora.co.uk>
Tue, 28 Jun 2011 06:51:23 +0000 (08:51 +0200)
committerSebastian Dröge <sebastian.droege@collabora.co.uk>
Sat, 9 Jul 2011 09:06:05 +0000 (11:06 +0200)
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
omx/gstomx.c
omx/gstomx.h [new file with mode: 0644]
omx/gstomxmpeg4videodec.c [new file with mode: 0644]
omx/gstomxmpeg4videodec.h [new file with mode: 0644]
omx/gstomxvideodec.c [new file with mode: 0644]
omx/gstomxvideodec.h [new file with mode: 0644]

index e69de29..1e43413 100644 (file)
@@ -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
+
index e69de29..fbf55ab 100644 (file)
+/*
+ * Copyright (C) 2011, Hewlett-Packard Development Company, L.P.
+ *   Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, 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 <gst/gst.h>
+#include <string.h>
+
+#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 (file)
index 0000000..a8405f5
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2011, Hewlett-Packard Development Company, L.P.
+ *   Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, 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 <gst/gst.h>
+#include <OMX_Core.h>
+#include <OMX_Component.h>
+
+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 (file)
index 0000000..55d992a
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011, Hewlett-Packard Development Company, L.P.
+ *   Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, 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 <gst/gst.h>
+
+#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 <sebastian.droege@collabora.co.uk>");
+}
+
+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 (file)
index 0000000..73a68d5
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2011, Hewlett-Packard Development Company, L.P.
+ *   Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, 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 <gst/gst.h>
+#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 (file)
index 0000000..8007182
--- /dev/null
@@ -0,0 +1,786 @@
+/*
+ * Copyright (C) 2011, Hewlett-Packard Development Company, L.P.
+ *   Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, 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 <gst/gst.h>
+#include <string.h>
+
+#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 (file)
index 0000000..0ac411c
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2011, Hewlett-Packard Development Company, L.P.
+ *   Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, 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 <gst/gst.h>
+#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__ */