/* * 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" #include "gstomxh264dec.h" #include "gstomxh263dec.h" #include "gstomxwmvdec.h" #include "gstomxmpeg4videoenc.h" #include "gstomxh264enc.h" #include "gstomxh263enc.h" #include "gstomxaacenc.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); gst_omx_rec_mutex_recursive_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); gst_omx_rec_mutex_recursive_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 */ gst_omx_rec_mutex_recursive_lock (&port->port_lock); /* FIXME: 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 { GST_ERROR_OBJECT (comp->parent, "Port %u was not flushing", port->index); } gst_omx_rec_mutex_recursive_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")); gst_omx_rec_mutex_recursive_lock (&port->port_lock); port->enabled_changed = TRUE; g_cond_broadcast (port->port_cond); gst_omx_rec_mutex_recursive_unlock (&port->port_lock); break; } default: break; } break; } case OMX_EventError: { OMX_ERRORTYPE err = nData1; if (err == OMX_ErrorNone) break; GST_ERROR_OBJECT (comp->parent, "Got error: %s (0x%08x)", gst_omx_error_to_string (err), err); /* Error events are always fatal */ gst_omx_component_set_last_error (comp, err); break; } case OMX_EventPortSettingsChanged: { gint i, n; guint32 port_index; GList *outports = NULL, *l, *k; if (!(comp->hacks & GST_OMX_HACK_EVENT_PORT_SETTINGS_CHANGED_NDATA_PARAMETER_SWAP)) { port_index = nData1; } else { port_index = nData2; } if (port_index == 0 && (comp->hacks & GST_OMX_HACK_EVENT_PORT_SETTINGS_CHANGED_PORT_0_TO_1)) port_index = 1; GST_DEBUG_OBJECT (comp->parent, "Settings changed (port index: %d)", port_index); /* Now update the ports' states */ n = (comp->ports ? comp->ports->len : 0); for (i = 0; i < n; i++) { GstOMXPort *port = g_ptr_array_index (comp->ports, i); gst_omx_rec_mutex_recursive_lock (&port->port_lock); if (port_index == OMX_ALL || port_index == port->index) { port->settings_cookie++; if (port->port_def.eDir == OMX_DirOutput) outports = g_list_prepend (outports, port); g_cond_broadcast (port->port_cond); } gst_omx_rec_mutex_recursive_unlock (&port->port_lock); } gst_omx_rec_mutex_recursive_lock (&comp->state_lock); for (k = outports; k; k = k->next) { gboolean found = FALSE; for (l = comp->pending_reconfigure_outports; l; l = l->next) { if (l->data == k->data) { found = TRUE; break; } } if (!found) comp->pending_reconfigure_outports = g_list_prepend (comp->pending_reconfigure_outports, k->data); } if (comp->pending_reconfigure_outports) g_atomic_int_set (&comp->have_pending_reconfigure_outports, 1); gst_omx_rec_mutex_recursive_unlock (&comp->state_lock); g_list_free (outports); 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; GstOMXPort *port; GstOMXComponent *comp; if (buf == NULL) { GST_ERROR ("Have unknown or deallocated buffer %p", pBuffer); return OMX_ErrorNone; } port = buf->port; comp = port->comp; g_assert (buf->omx_buf == pBuffer); /* Input buffer is empty again and can * be used to contain new input */ gst_omx_rec_mutex_recursive_lock (&port->port_lock); GST_DEBUG_OBJECT (comp->parent, "Port %u emptied buffer %p (%p)", port->index, buf, buf->omx_buf->pBuffer); buf->used = FALSE; /* XXX: Some OMX implementations don't reset nOffset * when the complete buffer is emptied but instead * only reset nFilledLen. We reset nOffset to 0 * if nFilledLen == 0, which is safe to do because * the offset *must* be 0 if the buffer is not * filled at all. * * Seen in QCOM's OMX implementation. */ if (buf->omx_buf->nFilledLen == 0) buf->omx_buf->nOffset = 0; /* Reset all flags, some implementations don't * reset them themselves and the flags are not * valid anymore after the buffer was consumed */ buf->omx_buf->nFlags = 0; g_queue_push_tail (port->pending_buffers, buf); g_cond_broadcast (port->port_cond); gst_omx_rec_mutex_recursive_unlock (&port->port_lock); return OMX_ErrorNone; } static OMX_ERRORTYPE FillBufferDone (OMX_HANDLETYPE hComponent, OMX_PTR pAppData, OMX_BUFFERHEADERTYPE * pBuffer) { GstOMXBuffer *buf = pBuffer->pAppPrivate; GstOMXPort *port; GstOMXComponent *comp; if (buf == NULL) { GST_ERROR ("Have unknown or deallocated buffer %p", pBuffer); return OMX_ErrorNone; } port = buf->port; comp = port->comp; g_assert (buf->omx_buf == pBuffer); /* Output buffer contains output now or * the port was flushed */ gst_omx_rec_mutex_recursive_lock (&port->port_lock); GST_DEBUG_OBJECT (comp->parent, "Port %u filled buffer %p (%p)", port->index, buf, buf->omx_buf->pBuffer); buf->used = FALSE; g_queue_push_tail (port->pending_buffers, buf); g_cond_broadcast (port->port_cond); gst_omx_rec_mutex_recursive_unlock (&port->port_lock); return OMX_ErrorNone; } static OMX_CALLBACKTYPE callbacks = { EventHandler, EmptyBufferDone, FillBufferDone }; GstOMXComponent * gst_omx_component_new (GstObject * parent, const GstOMXClassData * cdata) { OMX_ERRORTYPE err; GstOMXCore *core; GstOMXComponent *comp; core = gst_omx_core_acquire (cdata->core_name); if (!core) return NULL; comp = g_slice_new0 (GstOMXComponent); comp->core = core; err = core->get_handle (&comp->handle, (OMX_STRING) cdata->component_name, comp, &callbacks); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (parent, "Failed to get component handle '%s' from core '%s': 0x%08x", cdata->component_name, cdata->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, cdata->component_name, cdata->core_name); comp->parent = gst_object_ref (parent); comp->hacks = cdata->hacks; comp->ports = g_ptr_array_new (); comp->n_in_ports = 0; comp->n_out_ports = 0; gst_omx_rec_mutex_init (&comp->state_lock); comp->state_cond = g_cond_new (); comp->pending_state = OMX_StateInvalid; comp->last_error = OMX_ErrorNone; /* Set component role if any */ if (cdata->component_role && !(cdata->hacks & GST_OMX_HACK_NO_COMPONENT_ROLE)) { OMX_PARAM_COMPONENTROLETYPE param; GST_OMX_INIT_STRUCT (¶m); g_strlcpy ((gchar *) param.cRole, cdata->component_role, sizeof (param.cRole)); err = gst_omx_component_set_parameter (comp, OMX_IndexParamStandardComponentRole, ¶m); GST_DEBUG_OBJECT (parent, "Setting component role to '%s': %s (0x%08x)", cdata->component_role, gst_omx_error_to_string (err), err); /* If setting the role failed this component is unusable */ if (err != OMX_ErrorNone) { gst_omx_component_free (comp); return NULL; } } 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); gst_omx_port_deallocate_buffers (port); gst_omx_rec_mutex_clear (&port->port_lock); g_cond_free (port->port_cond); g_queue_free (port->pending_buffers); g_slice_free (GstOMXPort, port); } #if GLIB_CHECK_VERSION(2,22,0) g_ptr_array_unref (comp->ports); #else g_ptr_array_free (comp->ports, TRUE); #endif comp->ports = NULL; } comp->core->free_handle (comp->handle); gst_omx_core_release (comp->core); g_cond_free (comp->state_cond); gst_omx_rec_mutex_clear (&comp->state_lock); gst_object_unref (comp->parent); g_slice_free (GstOMXComponent, comp); } 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); gst_omx_rec_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 && state > old_state) { GST_ERROR_OBJECT (comp->parent, "Component in error state: %s (0x%08x)", gst_omx_error_to_string (err), err); goto done; } if (old_state == state || comp->pending_state == state) { GST_DEBUG_OBJECT (comp->parent, "Component already in state %d", state); goto done; } comp->pending_state = state; /* Reset some things */ if (old_state == OMX_StateExecuting && state < old_state) { g_atomic_int_set (&comp->have_pending_reconfigure_outports, 0); g_list_free (comp->pending_reconfigure_outports); comp->pending_reconfigure_outports = NULL; /* Notify all inports that are still waiting */ g_cond_broadcast (comp->state_cond); } gst_omx_rec_mutex_begin_recursion (&comp->state_lock); err = OMX_SendCommand (comp->handle, OMX_CommandStateSet, state, NULL); gst_omx_rec_mutex_end_recursion (&comp->state_lock); /* No need to check if anything has changed here */ done: gst_omx_rec_mutex_unlock (&comp->state_lock); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Error setting state from %d to %d: %s (0x%08x)", old_state, state, gst_omx_error_to_string (err), err); gst_omx_component_set_last_error (comp, err); } return err; } OMX_STATETYPE gst_omx_component_get_state (GstOMXComponent * comp, GstClockTime timeout) { OMX_STATETYPE ret; GTimeVal *timeval, abstimeout; gboolean signalled = TRUE; g_return_val_if_fail (comp != NULL, OMX_StateInvalid); GST_DEBUG_OBJECT (comp->parent, "Getting state"); gst_omx_rec_mutex_lock (&comp->state_lock); ret = comp->state; if (comp->pending_state == OMX_StateInvalid) goto done; if (comp->last_error != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Component in error state: %s (0x%08x)", gst_omx_error_to_string (comp->last_error), comp->last_error); ret = OMX_StateInvalid; goto done; } if (timeout != GST_CLOCK_TIME_NONE) { glong add = timeout / (GST_SECOND / G_USEC_PER_SEC); 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.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: %s (0x%08x)", gst_omx_error_to_string (comp->last_error), comp->last_error); ret = OMX_StateInvalid; } else if (comp->pending_state == OMX_StateInvalid) { /* State change finished and everything's fine */ ret = comp->state; } else { ret = OMX_StateInvalid; g_assert_not_reached (); } } else { ret = OMX_StateInvalid; GST_WARNING_OBJECT (comp->parent, "Timeout while waiting for state change"); } done: gst_omx_rec_mutex_unlock (&comp->state_lock); /* If we waited and timed out this component is unusable now */ if (!signalled) gst_omx_component_set_last_error (comp, OMX_ErrorTimeout); 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); GST_OMX_INIT_STRUCT (&port_def); port_def.nPortIndex = index; err = gst_omx_component_get_parameter (comp, OMX_IndexParamPortDefinition, &port_def); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Failed to add port %u: %s (0x%08x)", index, gst_omx_error_to_string (err), err); return NULL; } port = g_slice_new0 (GstOMXPort); port->comp = comp; port->index = index; port->port_def = port_def; gst_omx_rec_mutex_init (&port->port_lock); port->port_cond = g_cond_new (); port->pending_buffers = g_queue_new (); port->flushing = TRUE; port->flushed = FALSE; port->settings_changed = FALSE; port->enabled_changed = FALSE; if (port->port_def.eDir == OMX_DirInput) comp->n_in_ports++; else comp->n_out_ports++; 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; } void gst_omx_component_trigger_settings_changed (GstOMXComponent * comp, guint32 port_index) { g_return_if_fail (comp != NULL); /* Reverse hacks */ if (port_index == 1 && (comp->hacks & GST_OMX_HACK_EVENT_PORT_SETTINGS_CHANGED_PORT_0_TO_1)) port_index = 0; if (!(comp->hacks & GST_OMX_HACK_EVENT_PORT_SETTINGS_CHANGED_NDATA_PARAMETER_SWAP)) { EventHandler (comp->handle, comp, OMX_EventPortSettingsChanged, port_index, 0, NULL); } else { EventHandler (comp->handle, comp, OMX_EventPortSettingsChanged, 0, port_index, NULL); } } /* NOTE: This takes comp->state_lock *and* *all* port->port_lock! */ void gst_omx_component_set_last_error (GstOMXComponent * comp, OMX_ERRORTYPE err) { gint i, n; g_return_if_fail (comp != NULL); if (err == OMX_ErrorNone) return; GST_ERROR_OBJECT (comp->parent, "Setting last error: %s (0x%08x)", gst_omx_error_to_string (err), err); gst_omx_rec_mutex_lock (&comp->state_lock); /* We only set the first error ever from which * we can't recover anymore. */ if (comp->last_error == OMX_ErrorNone) comp->last_error = err; g_cond_broadcast (comp->state_cond); gst_omx_rec_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 ? comp->ports->len : 0); for (i = 0; i < n; i++) { GstOMXPort *tmp = g_ptr_array_index (comp->ports, i); gst_omx_rec_mutex_lock (&tmp->port_lock); g_cond_broadcast (tmp->port_cond); gst_omx_rec_mutex_unlock (&tmp->port_lock); } } OMX_ERRORTYPE gst_omx_component_get_last_error (GstOMXComponent * comp) { OMX_ERRORTYPE err; g_return_val_if_fail (comp != NULL, OMX_ErrorUndefined); gst_omx_rec_mutex_lock (&comp->state_lock); err = comp->last_error; gst_omx_rec_mutex_unlock (&comp->state_lock); GST_DEBUG_OBJECT (comp->parent, "Returning last error: %s (0x%08x)", gst_omx_error_to_string (err), err); return err; } const gchar * gst_omx_component_get_last_error_string (GstOMXComponent * comp) { g_return_val_if_fail (comp != NULL, NULL); return gst_omx_error_to_string (gst_omx_component_get_last_error (comp)); } OMX_ERRORTYPE gst_omx_component_get_parameter (GstOMXComponent * comp, OMX_INDEXTYPE index, gpointer param) { OMX_ERRORTYPE err; g_return_val_if_fail (comp != NULL, OMX_ErrorUndefined); g_return_val_if_fail (param != NULL, OMX_ErrorUndefined); GST_DEBUG_OBJECT (comp->parent, "Getting parameter at index 0x%08x", index); err = OMX_GetParameter (comp->handle, index, param); GST_DEBUG_OBJECT (comp->parent, "Got parameter at index 0x%08x: %s (0x%08x)", index, gst_omx_error_to_string (err), err); return err; } OMX_ERRORTYPE gst_omx_component_set_parameter (GstOMXComponent * comp, OMX_INDEXTYPE index, gpointer param) { OMX_ERRORTYPE err; g_return_val_if_fail (comp != NULL, OMX_ErrorUndefined); g_return_val_if_fail (param != NULL, OMX_ErrorUndefined); GST_DEBUG_OBJECT (comp->parent, "Setting parameter at index 0x%08x", index); err = OMX_SetParameter (comp->handle, index, param); GST_DEBUG_OBJECT (comp->parent, "Set parameter at index 0x%08x: %s (0x%08x)", index, gst_omx_error_to_string (err), err); return err; } OMX_ERRORTYPE gst_omx_component_get_config (GstOMXComponent * comp, OMX_INDEXTYPE index, gpointer config) { OMX_ERRORTYPE err; g_return_val_if_fail (comp != NULL, OMX_ErrorUndefined); g_return_val_if_fail (config != NULL, OMX_ErrorUndefined); GST_DEBUG_OBJECT (comp->parent, "Getting configuration at index 0x%08x", index); err = OMX_GetConfig (comp->handle, index, config); GST_DEBUG_OBJECT (comp->parent, "Got parameter at index 0x%08x: %s (0x%08x)", index, gst_omx_error_to_string (err), err); return err; } OMX_ERRORTYPE gst_omx_component_set_config (GstOMXComponent * comp, OMX_INDEXTYPE index, gpointer config) { OMX_ERRORTYPE err; g_return_val_if_fail (comp != NULL, OMX_ErrorUndefined); g_return_val_if_fail (config != NULL, OMX_ErrorUndefined); GST_DEBUG_OBJECT (comp->parent, "Setting configuration at index 0x%08x", index); err = OMX_SetConfig (comp->handle, index, config); GST_DEBUG_OBJECT (comp->parent, "Set parameter at index 0x%08x: %s (0x%08x)", index, gst_omx_error_to_string (err), err); return err; } void gst_omx_port_get_port_definition (GstOMXPort * port, OMX_PARAM_PORTDEFINITIONTYPE * port_def) { GstOMXComponent *comp; g_return_if_fail (port != NULL); comp = port->comp; GST_OMX_INIT_STRUCT (port_def); port_def->nPortIndex = port->index; gst_omx_component_get_parameter (comp, OMX_IndexParamPortDefinition, port_def); } gboolean gst_omx_port_update_port_definition (GstOMXPort * port, OMX_PARAM_PORTDEFINITIONTYPE * port_def) { OMX_ERRORTYPE err = OMX_ErrorNone; GstOMXComponent *comp; g_return_val_if_fail (port != NULL, FALSE); comp = port->comp; gst_omx_rec_mutex_lock (&port->port_lock); if (port_def) err = gst_omx_component_set_parameter (comp, OMX_IndexParamPortDefinition, port_def); gst_omx_component_get_parameter (comp, OMX_IndexParamPortDefinition, &port->port_def); GST_DEBUG_OBJECT (comp->parent, "Updated port %u definition: %s (0x%08x)", port->index, gst_omx_error_to_string (err), err); gst_omx_rec_mutex_unlock (&port->port_lock); return (err == OMX_ErrorNone); } GstOMXAcquireBufferReturn gst_omx_port_acquire_buffer (GstOMXPort * port, GstOMXBuffer ** buf) { GstOMXAcquireBufferReturn ret = GST_OMX_ACQUIRE_BUFFER_ERROR; GstOMXComponent *comp; OMX_ERRORTYPE err; GstOMXBuffer *_buf = NULL; g_return_val_if_fail (port != NULL, GST_OMX_ACQUIRE_BUFFER_ERROR); g_return_val_if_fail (buf != NULL, GST_OMX_ACQUIRE_BUFFER_ERROR); *buf = NULL; comp = port->comp; GST_DEBUG_OBJECT (comp->parent, "Acquiring buffer from port %u", port->index); gst_omx_rec_mutex_lock (&port->port_lock); retry: /* Check if the component is in an error state */ if ((err = gst_omx_component_get_last_error (comp)) != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Component is in error state: %s", gst_omx_error_to_string (err)); ret = GST_OMX_ACQUIRE_BUFFER_ERROR; goto done; } /* Check if the port is flushing */ if (port->flushing) { ret = GST_OMX_ACQUIRE_BUFFER_FLUSHING; goto done; } /* If this is an input port and at least one of the output ports * needs to be reconfigured, we wait until all output ports are * reconfigured. Afterwards this port is reconfigured if required * or buffers are returned to be filled as usual. */ if (port->port_def.eDir == OMX_DirInput) { if (g_atomic_int_get (&comp->have_pending_reconfigure_outports)) { gst_omx_rec_mutex_unlock (&port->port_lock); gst_omx_rec_mutex_lock (&comp->state_lock); while (g_atomic_int_get (&comp->have_pending_reconfigure_outports) && (err = comp->last_error) == OMX_ErrorNone && !port->flushing) { GST_DEBUG_OBJECT (comp->parent, "Waiting for output ports to reconfigure"); g_cond_wait (comp->state_cond, comp->state_lock.lock); } gst_omx_rec_mutex_unlock (&comp->state_lock); gst_omx_rec_mutex_lock (&port->port_lock); goto retry; } /* Only if this port needs to be reconfigured too notify * the caller about it */ if (port->settings_cookie != port->configured_settings_cookie) { ret = GST_OMX_ACQUIRE_BUFFER_RECONFIGURE; port->settings_changed = TRUE; goto done; } } /* If we have an output port that needs to be reconfigured * and it still has buffers pending for the old configuration * we first return them. * NOTE: If buffers for this configuration arrive later * we have to drop them... */ if (port->port_def.eDir == OMX_DirOutput && port->settings_cookie != port->configured_settings_cookie) { if (!g_queue_is_empty (port->pending_buffers)) { GST_DEBUG_OBJECT (comp->parent, "Output port %u needs reconfiguration but has buffers pending", port->index); _buf = g_queue_pop_head (port->pending_buffers); ret = GST_OMX_ACQUIRE_BUFFER_OK; goto done; } ret = GST_OMX_ACQUIRE_BUFFER_RECONFIGURE; port->settings_changed = TRUE; goto done; } if (port->settings_changed) { GST_DEBUG_OBJECT (comp->parent, "Port %u has settings changed, need new caps", port->index); ret = GST_OMX_ACQUIRE_BUFFER_RECONFIGURED; port->settings_changed = FALSE; goto done; } /* * At this point we have no error or flushing port * and a properly configured port. * */ /* If the queue is empty we wait until a buffer * arrives, an error happens, the port is flushing * or the port needs to be reconfigured. */ if (g_queue_is_empty (port->pending_buffers)) { GST_DEBUG_OBJECT (comp->parent, "Queue of port %u is empty", port->index); g_cond_wait (port->port_cond, port->port_lock.lock); } else { GST_DEBUG_OBJECT (comp->parent, "Port %u has pending buffers", port->pending_buffers); _buf = g_queue_pop_head (port->pending_buffers); ret = GST_OMX_ACQUIRE_BUFFER_OK; goto done; } /* And now check everything again and maybe get a buffer */ goto retry; done: gst_omx_rec_mutex_unlock (&port->port_lock); if (_buf) { g_assert (_buf == _buf->omx_buf->pAppPrivate); *buf = _buf; } GST_DEBUG_OBJECT (comp->parent, "Acquired buffer %p (%p) from port %u: %d", _buf, (_buf ? _buf->omx_buf->pBuffer : NULL), port->index, ret); return ret; } OMX_ERRORTYPE gst_omx_port_release_buffer (GstOMXPort * port, GstOMXBuffer * buf) { GstOMXComponent *comp; OMX_ERRORTYPE err = OMX_ErrorNone; g_return_val_if_fail (port != NULL, OMX_ErrorUndefined); g_return_val_if_fail (buf != NULL, OMX_ErrorUndefined); g_return_val_if_fail (buf->port == port, OMX_ErrorUndefined); comp = port->comp; GST_DEBUG_OBJECT (comp->parent, "Releasing buffer %p (%p) to port %u", buf, buf->omx_buf->pBuffer, port->index); gst_omx_rec_mutex_lock (&port->port_lock); if (port->port_def.eDir == OMX_DirInput) { /* Reset all flags, some implementations don't * reset them themselves and the flags are not * valid anymore after the buffer was consumed */ buf->omx_buf->nFlags = 0; } if ((err = gst_omx_component_get_last_error (comp)) != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Component is in error state: %s (0x%08x)", gst_omx_error_to_string (err), err); g_queue_push_tail (port->pending_buffers, buf); g_cond_broadcast (port->port_cond); goto done; } if (port->flushing) { GST_DEBUG_OBJECT (comp->parent, "Port %u is flushing, not releasing buffer", port->index); g_queue_push_tail (port->pending_buffers, buf); g_cond_broadcast (port->port_cond); goto done; } g_assert (buf == buf->omx_buf->pAppPrivate); /* FIXME: What if the settings cookies don't match? */ buf->used = TRUE; gst_omx_rec_mutex_begin_recursion (&port->port_lock); if (port->port_def.eDir == OMX_DirInput) { err = OMX_EmptyThisBuffer (comp->handle, buf->omx_buf); } else { err = OMX_FillThisBuffer (comp->handle, buf->omx_buf); } gst_omx_rec_mutex_end_recursion (&port->port_lock); GST_DEBUG_OBJECT (comp->parent, "Released buffer %p to port %u: %s (0x%08x)", buf, port->index, gst_omx_error_to_string (err), err); done: gst_omx_rec_mutex_unlock (&port->port_lock); return err; } OMX_ERRORTYPE gst_omx_port_set_flushing (GstOMXPort * port, gboolean flush) { GstOMXComponent *comp; OMX_ERRORTYPE err = OMX_ErrorNone; g_return_val_if_fail (port != NULL, OMX_ErrorUndefined); comp = port->comp; GST_DEBUG_OBJECT (comp->parent, "Setting port %d to %sflushing", port->index, (flush ? "" : "not ")); gst_omx_rec_mutex_lock (&port->port_lock); if (! !flush == ! !port->flushing) { GST_DEBUG_OBJECT (comp->parent, "Port %u was %sflushing already", port->index, (flush ? "" : "not ")); goto done; } if ((err = gst_omx_component_get_last_error (comp)) != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Component is in error state: %s (0x%08x)", gst_omx_error_to_string (err), err); goto done; } gst_omx_rec_mutex_lock (&comp->state_lock); if (comp->state != OMX_StateIdle && comp->state != OMX_StateExecuting) { GST_DEBUG_OBJECT (comp->parent, "Component is in wrong state: %d", comp->state); err = OMX_ErrorUndefined; gst_omx_rec_mutex_unlock (&comp->state_lock); goto done; } gst_omx_rec_mutex_unlock (&comp->state_lock); port->flushing = flush; if (flush) { GTimeVal abstimeout, *timeval; gboolean signalled; OMX_ERRORTYPE last_error; g_cond_broadcast (port->port_cond); /* We also need to signal the state cond because * an input port might wait on this for the output * ports to reconfigure. This will not confuse * other waiters on the state cond because they will * additionally check if the condition they're waiting * for is true after waking up. */ gst_omx_rec_mutex_lock (&comp->state_lock); g_cond_broadcast (comp->state_cond); gst_omx_rec_mutex_unlock (&comp->state_lock); /* Now flush the port */ port->flushed = FALSE; gst_omx_rec_mutex_begin_recursion (&port->port_lock); err = OMX_SendCommand (comp->handle, OMX_CommandFlush, port->index, NULL); gst_omx_rec_mutex_end_recursion (&port->port_lock); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Error sending flush command to port %u: %s (0x%08x)", port->index, gst_omx_error_to_string (err), err); goto done; } if ((err = gst_omx_component_get_last_error (comp)) != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Component is in error state: %s (0x%08x)", gst_omx_error_to_string (err), err); goto done; } if (! !port->flushing != ! !flush) { GST_ERROR_OBJECT (comp->parent, "Another flush happened in the meantime"); goto done; } g_get_current_time (&abstimeout); g_time_val_add (&abstimeout, 5 * G_USEC_PER_SEC); timeval = &abstimeout; GST_DEBUG_OBJECT (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 */ signalled = TRUE; last_error = OMX_ErrorNone; while (signalled && last_error == OMX_ErrorNone && !port->flushed && port->buffers->len > g_queue_get_length (port->pending_buffers)) { signalled = g_cond_timed_wait (port->port_cond, port->port_lock.lock, timeval); last_error = gst_omx_component_get_last_error (comp); } port->flushed = FALSE; GST_DEBUG_OBJECT (comp->parent, "Port %d flushed", port->index); if (last_error != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Got error while flushing port %u: %s (0x%08x)", port->index, gst_omx_error_to_string (last_error), last_error); err = last_error; goto done; } else if (!signalled) { GST_ERROR_OBJECT (comp->parent, "Timeout while flushing port %u", port->index); err = OMX_ErrorTimeout; goto done; } } else { if (port->port_def.eDir == OMX_DirOutput && port->buffers) { GstOMXBuffer *buf; /* Enqueue all buffers for the component to fill */ while ((buf = g_queue_pop_head (port->pending_buffers))) { if (!buf) continue; g_assert (!buf->used); /* Reset all flags, some implementations don't * reset them themselves and the flags are not * valid anymore after the buffer was consumed */ buf->omx_buf->nFlags = 0; gst_omx_rec_mutex_begin_recursion (&port->port_lock); err = OMX_FillThisBuffer (comp->handle, buf->omx_buf); gst_omx_rec_mutex_end_recursion (&port->port_lock); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Failed to pass buffer %p (%p) to port %u: %s (0x%08x)", buf, buf->omx_buf->pBuffer, port->index, gst_omx_error_to_string (err), err); goto error; } GST_DEBUG_OBJECT (comp->parent, "Passed buffer %p (%p) to component", buf, buf->omx_buf->pBuffer); } } } done: GST_DEBUG_OBJECT (comp->parent, "Set port %u to %sflushing: %s (0x%08x)", port->index, (flush ? "" : "not "), gst_omx_error_to_string (err), err); gst_omx_rec_mutex_unlock (&port->port_lock); return err; error: { /* Need to unlock the port lock here because * set_last_error() needs all port locks. * This is safe here because we're just going * to error out anyway */ gst_omx_rec_mutex_unlock (&port->port_lock); gst_omx_component_set_last_error (comp, err); gst_omx_rec_mutex_lock (&port->port_lock); goto done; } } gboolean gst_omx_port_is_flushing (GstOMXPort * port) { GstOMXComponent *comp; gboolean flushing; g_return_val_if_fail (port != NULL, FALSE); comp = port->comp; gst_omx_rec_mutex_lock (&port->port_lock); flushing = port->flushing; gst_omx_rec_mutex_unlock (&port->port_lock); GST_DEBUG_OBJECT (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) { GstOMXComponent *comp; OMX_ERRORTYPE err = OMX_ErrorNone; gint i, n; g_assert (!port->buffers || port->buffers->len == 0); comp = port->comp; if ((err = gst_omx_component_get_last_error (comp)) != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Component in error state: %s (0x%08x)", gst_omx_error_to_string (err), err); goto done; } /* Update the port definition to check if we need more * buffers after the port configuration was done and to * update the buffer size */ gst_omx_component_get_parameter (comp, 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; err = gst_omx_component_set_parameter (comp, OMX_IndexParamPortDefinition, &port->port_def); gst_omx_component_get_parameter (comp, OMX_IndexParamPortDefinition, &port->port_def); } if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Failed to configure number of buffers of port %u: %s (0x%08x)", port->index, gst_omx_error_to_string (err), err); goto error; } n = port->port_def.nBufferCountActual; GST_DEBUG_OBJECT (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; buf->settings_cookie = port->settings_cookie; g_ptr_array_add (port->buffers, buf); gst_omx_rec_mutex_begin_recursion (&port->port_lock); err = OMX_AllocateBuffer (comp->handle, &buf->omx_buf, port->index, buf, port->port_def.nBufferSize); gst_omx_rec_mutex_end_recursion (&port->port_lock); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Failed to allocate buffer for port %u: %s (0x%08x)", port->index, gst_omx_error_to_string (err), err); goto error; } GST_DEBUG_OBJECT (comp->parent, "Allocated buffer %p (%p)", buf, buf->omx_buf->pBuffer); g_assert (buf->omx_buf->pAppPrivate == buf); /* In the beginning all buffers are not owned by the component */ g_queue_push_tail (port->pending_buffers, buf); } done: GST_DEBUG_OBJECT (comp->parent, "Allocated buffers for port %u: %s (0x%08x)", port->index, gst_omx_error_to_string (err), err); return err; error: { /* Need to unlock the port lock here because * set_last_error() needs all port locks. * This is safe here because we're just going * to error out anyway */ gst_omx_rec_mutex_unlock (&port->port_lock); gst_omx_component_set_last_error (comp, err); gst_omx_rec_mutex_lock (&port->port_lock); goto done; } } OMX_ERRORTYPE gst_omx_port_allocate_buffers (GstOMXPort * port) { OMX_ERRORTYPE err; g_return_val_if_fail (port != NULL, OMX_ErrorUndefined); gst_omx_rec_mutex_lock (&port->port_lock); err = gst_omx_port_allocate_buffers_unlocked (port); gst_omx_rec_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) { GstOMXComponent *comp; OMX_ERRORTYPE err = OMX_ErrorNone; gint i, n; comp = port->comp; GST_DEBUG_OBJECT (comp->parent, "Deallocating buffers of port %u", port->index); if (!port->buffers) { GST_DEBUG_OBJECT (comp->parent, "No buffers allocated for port %u", port->index); goto done; } if ((err = gst_omx_component_get_last_error (comp)) != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Component in error state: %s (0x%08x)", gst_omx_error_to_string (err), err); /* We still try to deallocate all buffers */ } /* We only allow deallocation of buffers after they * were all released from the port, either by flushing * the port or by disabling it. */ n = port->buffers->len; for (i = 0; i < n; i++) { GstOMXBuffer *buf = g_ptr_array_index (port->buffers, i); OMX_ERRORTYPE tmp = OMX_ErrorNone; if (buf->used) GST_ERROR_OBJECT (comp->parent, "Trying to free used buffer %p of port %u", buf, port->index); /* 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) { g_assert (buf == buf->omx_buf->pAppPrivate); buf->omx_buf->pAppPrivate = NULL; GST_DEBUG_OBJECT (comp->parent, "Deallocating buffer %p (%p)", buf, buf->omx_buf->pBuffer); gst_omx_rec_mutex_begin_recursion (&port->port_lock); tmp = OMX_FreeBuffer (comp->handle, port->index, buf->omx_buf); gst_omx_rec_mutex_end_recursion (&port->port_lock); if (tmp != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Failed to deallocate buffer %d of port %u: %s (0x%08x)", i, port->index, gst_omx_error_to_string (tmp), tmp); if (err == OMX_ErrorNone) err = tmp; } } g_slice_free (GstOMXBuffer, buf); } g_queue_clear (port->pending_buffers); #if GLIB_CHECK_VERSION(2,22,0) g_ptr_array_unref (port->buffers); #else g_ptr_array_free (port->buffers, TRUE); #endif port->buffers = NULL; done: GST_DEBUG_OBJECT (comp->parent, "Deallocated buffers of port %u: %s (0x%08x)", port->index, gst_omx_error_to_string (err), err); return err; } OMX_ERRORTYPE gst_omx_port_deallocate_buffers (GstOMXPort * port) { OMX_ERRORTYPE err; g_return_val_if_fail (port != NULL, OMX_ErrorUndefined); gst_omx_rec_mutex_lock (&port->port_lock); err = gst_omx_port_deallocate_buffers_unlocked (port); gst_omx_rec_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) { GstOMXComponent *comp; OMX_ERRORTYPE err = OMX_ErrorNone; GTimeVal abstimeout, *timeval; gboolean signalled; OMX_ERRORTYPE last_error; comp = port->comp; if ((err = gst_omx_component_get_last_error (comp)) != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Component in error state: %s (0x%08x)", gst_omx_error_to_string (err), err); goto done; } GST_DEBUG_OBJECT (comp->parent, "Setting port %u to %s", port->index, (enabled ? "enabled" : "disabled")); /* Check if the port is already enabled/disabled first */ gst_omx_component_get_parameter (comp, OMX_IndexParamPortDefinition, &port->port_def); if (! !port->port_def.bEnabled == ! !enabled) goto done; port->enabled_changed = FALSE; if (!enabled) { /* This is also like flushing, i.e. all buffers are returned * by the component and no new buffers should be passed to * the component anymore */ port->flushing = TRUE; } gst_omx_rec_mutex_begin_recursion (&port->port_lock); if (enabled) err = OMX_SendCommand (comp->handle, OMX_CommandPortEnable, port->index, NULL); else err = OMX_SendCommand (comp->handle, OMX_CommandPortDisable, port->index, NULL); gst_omx_rec_mutex_end_recursion (&port->port_lock); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Failed to send enable/disable command to port %u: %s (0x%08x)", port->index, gst_omx_error_to_string (err), err); goto error; } if ((err = gst_omx_component_get_last_error (comp)) != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Component in error state: %s (0x%08x)", gst_omx_error_to_string (err), err); goto done; } if (port->enabled_changed) { GST_ERROR_OBJECT (comp->parent, "Port enabled/disabled in the meantime"); goto done; } g_get_current_time (&abstimeout); g_time_val_add (&abstimeout, 5 * G_USEC_PER_SEC); timeval = &abstimeout; GST_DEBUG_OBJECT (comp->parent, "Waiting for 5s"); /* 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.lock, timeval); last_error = gst_omx_component_get_last_error (comp); } if (last_error != OMX_ErrorNone) { err = last_error; GST_ERROR_OBJECT (comp->parent, "Got error while waiting for port %u to release all buffers: %s (0x%08x)", port->index, gst_omx_error_to_string (err), err); goto done; } else if (!signalled) { GST_ERROR_OBJECT (comp->parent, "Timeout waiting for port %u to release all buffers", port->index); err = OMX_ErrorTimeout; goto error; } /* 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) { goto error; } } else { /* If deallocation fails this component can't really be used anymore */ if ((err = gst_omx_port_deallocate_buffers_unlocked (port)) != OMX_ErrorNone) { goto error; } } /* And now wait until the enable/disable command is finished */ signalled = TRUE; last_error = OMX_ErrorNone; gst_omx_component_get_parameter (comp, OMX_IndexParamPortDefinition, &port->port_def); while (signalled && last_error == OMX_ErrorNone && (! !port->port_def.bEnabled != ! !enabled || !port->enabled_changed)) { signalled = g_cond_timed_wait (port->port_cond, port->port_lock.lock, timeval); last_error = gst_omx_component_get_last_error (comp); gst_omx_component_get_parameter (comp, OMX_IndexParamPortDefinition, &port->port_def); } if (!signalled) { GST_ERROR_OBJECT (comp->parent, "Timeout waiting for port %u to be %s", port->index, (enabled ? "enabled" : "disabled")); err = OMX_ErrorTimeout; goto error; } else if (last_error != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Got error while waiting for port %u to be %s: %s (0x%08x)", port->index, (enabled ? "enabled" : "disabled"), gst_omx_error_to_string (err), err); err = last_error; } else { if (enabled) port->flushing = FALSE; /* If everything went fine and we have an output port we * should provide all newly allocated buffers to the port */ if (enabled && port->port_def.eDir == OMX_DirOutput) { GstOMXBuffer *buf; /* Enqueue all buffers for the component to fill */ while ((buf = g_queue_pop_head (port->pending_buffers))) { if (!buf) continue; g_assert (!buf->used); /* Reset all flags, some implementations don't * reset them themselves and the flags are not * valid anymore after the buffer was consumed */ buf->omx_buf->nFlags = 0; gst_omx_rec_mutex_begin_recursion (&port->port_lock); err = OMX_FillThisBuffer (comp->handle, buf->omx_buf); gst_omx_rec_mutex_end_recursion (&port->port_lock); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (comp->parent, "Failed to pass buffer %p (%p) to port %u: %s (0x%08x)", buf, buf->omx_buf->pBuffer, port->index, gst_omx_error_to_string (err), err); goto error; } GST_DEBUG_OBJECT (comp->parent, "Passed buffer %p (%p) to component", buf, buf->omx_buf->pBuffer); } } } done: GST_DEBUG_OBJECT (comp->parent, "Port %u is %s%s: %s (0x%08x)", port->index, (err == OMX_ErrorNone ? "" : "not "), (enabled ? "enabled" : "disabled"), gst_omx_error_to_string (err), err); return err; error: { /* Need to unlock the port lock here because * set_last_error() needs all port locks. * This is safe here because we're just going * to error out anyway */ gst_omx_rec_mutex_unlock (&port->port_lock); gst_omx_component_set_last_error (comp, err); gst_omx_rec_mutex_lock (&port->port_lock); goto done; } } OMX_ERRORTYPE gst_omx_port_set_enabled (GstOMXPort * port, gboolean enabled) { OMX_ERRORTYPE err; g_return_val_if_fail (port != NULL, OMX_ErrorUndefined); gst_omx_rec_mutex_lock (&port->port_lock); err = gst_omx_port_set_enabled_unlocked (port, enabled); gst_omx_rec_mutex_unlock (&port->port_lock); return err; } gboolean gst_omx_port_is_enabled (GstOMXPort * port) { GstOMXComponent *comp; gboolean enabled; g_return_val_if_fail (port != NULL, FALSE); comp = port->comp; gst_omx_rec_mutex_lock (&port->port_lock); gst_omx_component_get_parameter (comp, OMX_IndexParamPortDefinition, &port->port_def); enabled = port->port_def.bEnabled; gst_omx_rec_mutex_unlock (&port->port_lock); GST_DEBUG_OBJECT (comp->parent, "Port %u is enabled: %d", port->index, enabled); return enabled; } OMX_ERRORTYPE gst_omx_port_reconfigure (GstOMXPort * port) { GstOMXComponent *comp; OMX_ERRORTYPE err = OMX_ErrorNone; g_return_val_if_fail (port != NULL, OMX_ErrorUndefined); comp = port->comp; GST_DEBUG_OBJECT (comp->parent, "Reconfiguring port %u", port->index); gst_omx_rec_mutex_lock (&port->port_lock); if (!port->settings_changed) goto done; if ((err = gst_omx_component_get_last_error (comp)) != OMX_ErrorNone) 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->configured_settings_cookie = port->settings_cookie; /* If this is an output port, notify all input ports * that might wait for us to reconfigure in * acquire_buffer() */ if (port->port_def.eDir == OMX_DirOutput) { GList *l; gst_omx_rec_mutex_lock (&comp->state_lock); for (l = comp->pending_reconfigure_outports; l; l = l->next) { if (l->data == (gpointer) port) { comp->pending_reconfigure_outports = g_list_delete_link (comp->pending_reconfigure_outports, l); break; } } if (!comp->pending_reconfigure_outports) { g_atomic_int_set (&comp->have_pending_reconfigure_outports, 0); g_cond_broadcast (comp->state_cond); } gst_omx_rec_mutex_unlock (&comp->state_lock); } done: GST_DEBUG_OBJECT (comp->parent, "Reconfigured port %u: %s (0x%08x)", port->index, gst_omx_error_to_string (err), err); gst_omx_rec_mutex_unlock (&port->port_lock); return err; } OMX_ERRORTYPE gst_omx_port_manual_reconfigure (GstOMXPort * port, gboolean start) { GstOMXComponent *comp; OMX_ERRORTYPE err = OMX_ErrorNone; g_return_val_if_fail (port != NULL, OMX_ErrorUndefined); comp = port->comp; GST_DEBUG_OBJECT (comp->parent, "Manual reconfigure of port %u %s", port->index, (start ? "start" : "stsop")); gst_omx_rec_mutex_lock (&port->port_lock); if ((err = gst_omx_component_get_last_error (comp)) != OMX_ErrorNone) goto done; if (start) port->settings_cookie++; else port->configured_settings_cookie = port->settings_cookie; if (port->port_def.eDir == OMX_DirOutput) { GList *l; if (start) { gst_omx_rec_mutex_lock (&comp->state_lock); for (l = comp->pending_reconfigure_outports; l; l = l->next) { if (l->data == (gpointer) port) break; } if (!l) { comp->pending_reconfigure_outports = g_list_prepend (comp->pending_reconfigure_outports, port); g_atomic_int_set (&comp->have_pending_reconfigure_outports, 1); } gst_omx_rec_mutex_unlock (&comp->state_lock); } else { gst_omx_rec_mutex_lock (&comp->state_lock); for (l = comp->pending_reconfigure_outports; l; l = l->next) { if (l->data == (gpointer) port) { comp->pending_reconfigure_outports = g_list_delete_link (comp->pending_reconfigure_outports, l); break; } } if (!comp->pending_reconfigure_outports) { g_atomic_int_set (&comp->have_pending_reconfigure_outports, 0); g_cond_broadcast (comp->state_cond); } gst_omx_rec_mutex_unlock (&comp->state_lock); } } done: gst_omx_rec_mutex_unlock (&port->port_lock); GST_DEBUG_OBJECT (comp->parent, "Manual reconfigure of port %u: %s (0x%08x)", port->index, gst_omx_error_to_string (err), err); return err; } static GType (*types[]) (void) = { gst_omx_mpeg4_video_dec_get_type, gst_omx_h264_dec_get_type, gst_omx_h263_dec_get_type, gst_omx_wmv_dec_get_type, gst_omx_mpeg4_video_enc_get_type, gst_omx_h264_enc_get_type, gst_omx_h263_enc_get_type, gst_omx_aac_enc_get_type}; struct TypeOffest { GType (*get_type) (void); glong offset; }; static struct TypeOffest base_types[] = { {gst_omx_video_dec_get_type, G_STRUCT_OFFSET (GstOMXVideoDecClass, cdata)}, {gst_omx_video_enc_get_type, G_STRUCT_OFFSET (GstOMXVideoEncClass, cdata)}, {gst_omx_audio_enc_get_type, G_STRUCT_OFFSET (GstOMXAudioEncClass, cdata)}, }; static GKeyFile *config = NULL; GKeyFile * gst_omx_get_configuration (void) { return config; } const gchar * gst_omx_error_to_string (OMX_ERRORTYPE err) { switch (err) { case OMX_ErrorNone: return "None"; case OMX_ErrorInsufficientResources: return "Insufficient resources"; case OMX_ErrorUndefined: return "Undefined"; case OMX_ErrorInvalidComponentName: return "Invalid component name"; case OMX_ErrorComponentNotFound: return "Component not found"; case OMX_ErrorInvalidComponent: return "Invalid component"; case OMX_ErrorBadParameter: return "Bad parameter"; case OMX_ErrorNotImplemented: return "Not implemented"; case OMX_ErrorUnderflow: return "Underflow"; case OMX_ErrorOverflow: return "Overflow"; case OMX_ErrorHardware: return "Hardware"; case OMX_ErrorInvalidState: return "Invalid state"; case OMX_ErrorStreamCorrupt: return "Stream corrupt"; case OMX_ErrorPortsNotCompatible: return "Ports not compatible"; case OMX_ErrorResourcesLost: return "Resources lost"; case OMX_ErrorNoMore: return "No more"; case OMX_ErrorVersionMismatch: return "Version mismatch"; case OMX_ErrorNotReady: return "Not ready"; case OMX_ErrorTimeout: return "Timeout"; case OMX_ErrorSameState: return "Same state"; case OMX_ErrorResourcesPreempted: return "Resources preempted"; case OMX_ErrorPortUnresponsiveDuringAllocation: return "Port unresponsive during allocation"; case OMX_ErrorPortUnresponsiveDuringDeallocation: return "Port unresponsive during deallocation"; case OMX_ErrorPortUnresponsiveDuringStop: return "Port unresponsive during stop"; case OMX_ErrorIncorrectStateTransition: return "Incorrect state transition"; case OMX_ErrorIncorrectStateOperation: return "Incorrect state operation"; case OMX_ErrorUnsupportedSetting: return "Unsupported setting"; case OMX_ErrorUnsupportedIndex: return "Unsupported index"; case OMX_ErrorBadPortIndex: return "Bad port index"; case OMX_ErrorPortUnpopulated: return "Port unpopulated"; case OMX_ErrorComponentSuspended: return "Component suspended"; case OMX_ErrorDynamicResourcesUnavailable: return "Dynamic resources unavailable"; case OMX_ErrorMbErrorsInFrame: return "Macroblock errors in frame"; case OMX_ErrorFormatNotDetected: return "Format not detected"; case OMX_ErrorContentPipeOpenFailed: return "Content pipe open failed"; case OMX_ErrorContentPipeCreationFailed: return "Content pipe creation failed"; case OMX_ErrorSeperateTablesUsed: return "Separate tables used"; case OMX_ErrorTunnelingUnsupported: return "Tunneling unsupported"; default: if (err >= (guint32) OMX_ErrorKhronosExtensions && err < (guint32) OMX_ErrorVendorStartUnused) { return "Khronos extension error"; } else if (err >= (guint32) OMX_ErrorVendorStartUnused && err < (guint32) OMX_ErrorMax) { return "Vendor specific error"; } else { return "Unknown error"; } } } guint64 gst_omx_parse_hacks (gchar ** hacks) { guint64 hacks_flags = 0; if (!hacks) return 0; while (*hacks) { if (g_str_equal (*hacks, "event-port-settings-changed-ndata-parameter-swap")) hacks_flags |= GST_OMX_HACK_EVENT_PORT_SETTINGS_CHANGED_NDATA_PARAMETER_SWAP; else if (g_str_equal (*hacks, "event-port-settings-changed-port-0-to-1")) hacks_flags |= GST_OMX_HACK_EVENT_PORT_SETTINGS_CHANGED_PORT_0_TO_1; else if (g_str_equal (*hacks, "video-framerate-integer")) hacks_flags |= GST_OMX_HACK_VIDEO_FRAMERATE_INTEGER; else if (g_str_equal (*hacks, "syncframe-flag-not-used")) hacks_flags |= GST_OMX_HACK_SYNCFRAME_FLAG_NOT_USED; else if (g_str_equal (*hacks, "no-component-reconfigure")) hacks_flags |= GST_OMX_HACK_NO_COMPONENT_RECONFIGURE; else if (g_str_equal (*hacks, "no-empty-eos-buffer")) hacks_flags |= GST_OMX_HACK_NO_EMPTY_EOS_BUFFER; else if (g_str_equal (*hacks, "drain-may-not-return")) hacks_flags |= GST_OMX_HACK_DRAIN_MAY_NOT_RETURN; else if (g_str_equal (*hacks, "no-component-role")) hacks_flags |= GST_OMX_HACK_NO_COMPONENT_ROLE; else GST_WARNING ("Unknown hack: %s", *hacks); hacks++; } return hacks_flags; } void gst_omx_set_default_role (GstOMXClassData * class_data, const gchar * default_role) { if (!class_data->component_role) class_data->component_role = default_role; } static void _class_init (gpointer g_class, gpointer data) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); GstOMXClassData *class_data = NULL; GKeyFile *config; const gchar *element_name = data; GError *err; gchar *core_name, *component_name, *component_role; gint in_port_index, out_port_index; gchar *template_caps; GstPadTemplate *templ; GstCaps *caps; gchar **hacks; int i; if (!element_name) return; /* Find the GstOMXClassData for this class */ for (i = 0; i < G_N_ELEMENTS (base_types); i++) { GType gtype = base_types[i].get_type (); if (G_TYPE_CHECK_CLASS_TYPE (g_class, gtype)) { class_data = (GstOMXClassData *) (((guint8 *) g_class) + base_types[i].offset); break; } } g_assert (class_data != NULL); config = gst_omx_get_configuration (); /* This will alwaxys succeed, see check in plugin_init */ core_name = g_key_file_get_string (config, element_name, "core-name", NULL); g_assert (core_name != NULL); class_data->core_name = core_name; component_name = g_key_file_get_string (config, element_name, "component-name", NULL); g_assert (component_name != NULL); class_data->component_name = component_name; /* If this fails we simply don't set a role */ if ((component_role = g_key_file_get_string (config, element_name, "component-role", NULL))) { GST_DEBUG ("Using component-role '%s' for element '%s'", component_role, element_name); class_data->component_role = component_role; } /* Now set the inport/outport indizes and assume sane defaults */ err = NULL; in_port_index = g_key_file_get_integer (config, element_name, "in-port-index", &err); if (err != NULL) { GST_DEBUG ("No 'in-port-index' set for element '%s', assuming 0: %s", element_name, err->message); in_port_index = 0; g_error_free (err); } class_data->in_port_index = in_port_index; err = NULL; out_port_index = g_key_file_get_integer (config, element_name, "out-port-index", &err); if (err != NULL) { GST_DEBUG ("No 'out-port-index' set for element '%s', assuming 1: %s", element_name, err->message); out_port_index = 1; g_error_free (err); } class_data->out_port_index = out_port_index; /* Add pad templates */ err = NULL; if (!(template_caps = g_key_file_get_string (config, element_name, "sink-template-caps", &err))) { GST_DEBUG ("No sink template caps specified for element '%s', using default '%s'", element_name, class_data->default_sink_template_caps); caps = gst_caps_from_string (class_data->default_sink_template_caps); g_assert (caps != NULL); g_error_free (err); } else { caps = gst_caps_from_string (template_caps); if (!caps) { GST_DEBUG ("Could not parse sink template caps '%s' for element '%s', using default '%s'", template_caps, element_name, class_data->default_sink_template_caps); caps = gst_caps_from_string (class_data->default_sink_template_caps); g_assert (caps != NULL); } } templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, caps); g_free (template_caps); gst_element_class_add_pad_template (element_class, templ); err = NULL; if (!(template_caps = g_key_file_get_string (config, element_name, "src-template-caps", &err))) { GST_DEBUG ("No src template caps specified for element '%s', using default '%s'", element_name, class_data->default_src_template_caps); caps = gst_caps_from_string (class_data->default_src_template_caps); g_assert (caps != NULL); g_error_free (err); } else { caps = gst_caps_from_string (template_caps); if (!caps) { GST_DEBUG ("Could not parse src template caps '%s' for element '%s', using default '%s'", template_caps, element_name, class_data->default_src_template_caps); caps = gst_caps_from_string (class_data->default_src_template_caps); g_assert (caps != NULL); } } templ = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, caps); g_free (template_caps); gst_element_class_add_pad_template (element_class, templ); if ((hacks = g_key_file_get_string_list (config, element_name, "hacks", NULL, NULL))) { #ifndef GST_DISABLE_GST_DEBUG gchar **walk = hacks; while (*walk) { GST_DEBUG ("Using hack: %s", *walk); walk++; } #endif class_data->hacks = gst_omx_parse_hacks (hacks); } } static gboolean plugin_init (GstPlugin * plugin) { gboolean ret = FALSE; GError *err = NULL; gchar **config_dirs; gchar **elements; gchar *env_config_dir; const gchar *user_config_dir; const gchar *const *system_config_dirs; gint i, j; gsize n_elements; static const gchar *config_name[] = { "gstomx.conf", NULL }; static const gchar *env_config_name[] = { "GST_OMX_CONFIG_DIR", NULL }; GST_DEBUG_CATEGORY_INIT (gstomx_debug, "omx", 0, "gst-omx"); /* Read configuration file gstomx.conf from the preferred * configuration directories */ env_config_dir = g_strdup (g_getenv (*env_config_name)); user_config_dir = g_get_user_config_dir (); system_config_dirs = g_get_system_config_dirs (); config_dirs = g_new (gchar *, g_strv_length ((gchar **) system_config_dirs) + 3); i = 0; j = 0; if (env_config_dir) config_dirs[i++] = (gchar *) env_config_dir; config_dirs[i++] = (gchar *) user_config_dir; while (system_config_dirs[j]) config_dirs[i++] = (gchar *) system_config_dirs[j++]; config_dirs[i++] = NULL; gst_plugin_add_dependency (plugin, env_config_name, (const gchar **) (config_dirs + (env_config_dir ? 1 : 0)), config_name, GST_PLUGIN_DEPENDENCY_FLAG_NONE); config = g_key_file_new (); if (!g_key_file_load_from_dirs (config, *config_name, (const gchar **) config_dirs, NULL, G_KEY_FILE_NONE, &err)) { GST_ERROR ("Failed to load configuration file: %s", err->message); g_error_free (err); goto done; } /* Initialize all types */ for (i = 0; i < G_N_ELEMENTS (types); i++) types[i] (); elements = g_key_file_get_groups (config, &n_elements); for (i = 0; i < n_elements; i++) { GTypeQuery type_query; GTypeInfo type_info = { 0, }; GType type, subtype; gchar *type_name, *core_name, *component_name; gint rank; GST_DEBUG ("Registering element '%s'", elements[i]); err = NULL; if (!(type_name = g_key_file_get_string (config, elements[i], "type-name", &err))) { GST_ERROR ("Unable to read 'type-name' configuration for element '%s': %s", elements[i], err->message); g_error_free (err); continue; } type = g_type_from_name (type_name); if (type == G_TYPE_INVALID) { GST_ERROR ("Invalid type name '%s' for element '%s'", type_name, elements[i]); g_free (type_name); continue; } if (!g_type_is_a (type, GST_TYPE_ELEMENT)) { GST_ERROR ("Type '%s' is no GstElement subtype for element '%s'", type_name, elements[i]); g_free (type_name); continue; } g_free (type_name); /* And now some sanity checking */ err = NULL; if (!(core_name = g_key_file_get_string (config, elements[i], "core-name", &err))) { GST_ERROR ("Unable to read 'core-name' configuration for element '%s': %s", elements[i], err->message); g_error_free (err); continue; } if (!g_file_test (core_name, G_FILE_TEST_IS_REGULAR)) { GST_ERROR ("Core '%s' does not exist for element '%s'", core_name, elements[i]); g_free (core_name); continue; } g_free (core_name); err = NULL; if (!(component_name = g_key_file_get_string (config, elements[i], "component-name", &err))) { GST_ERROR ("Unable to read 'component-name' configuration for element '%s': %s", elements[i], err->message); g_error_free (err); continue; } g_free (component_name); err = NULL; rank = g_key_file_get_integer (config, elements[i], "rank", &err); if (err != NULL) { GST_ERROR ("No rank set for element '%s': %s", elements[i], err->message); g_error_free (err); continue; } /* And now register the type, all other configuration will * be handled by the type itself */ g_type_query (type, &type_query); memset (&type_info, 0, sizeof (type_info)); type_info.class_size = type_query.class_size; type_info.instance_size = type_query.instance_size; type_info.class_init = _class_init; type_info.class_data = g_strdup (elements[i]); type_name = g_strdup_printf ("%s-%s", g_type_name (type), elements[i]); if (g_type_from_name (type_name) != G_TYPE_INVALID) { GST_ERROR ("Type '%s' already exists for element '%s'", type_name, elements[i]); g_free (type_name); continue; } subtype = g_type_register_static (type, type_name, &type_info, 0); g_free (type_name); ret |= gst_element_register (plugin, elements[i], rank, subtype); } g_strfreev (elements); done: g_free (env_config_dir); g_free (config_dirs); return ret; } GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, "openmax", "GStreamer OpenMAX Plug-ins", plugin_init, PACKAGE_VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)