/* * Copyright (C) 2014, Fluendo, S.A. * Copyright (C) 2014, Metrological Media Innovations B.V. * Author: Josep Torra * * 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 #include "gstomxaudiosink.h" GST_DEBUG_CATEGORY_STATIC (gst_omx_audio_sink_debug_category); #define GST_CAT_DEFAULT gst_omx_audio_sink_debug_category #define DEBUG_INIT \ GST_DEBUG_CATEGORY_INIT (gst_omx_audio_sink_debug_category, "omxaudiosink", \ 0, "debug category for gst-omx audio sink base class"); #define DEFAULT_PROP_MUTE FALSE #define DEFAULT_PROP_VOLUME 1.0 #define VOLUME_MAX_DOUBLE 10.0 #define OUT_CHANNELS(num_channels) ((num_channels) > 4 ? 8: (num_channels) > 2 ? 4: (num_channels)) enum { PROP_0, PROP_MUTE, PROP_VOLUME }; #define gst_omx_audio_sink_parent_class parent_class G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstOMXAudioSink, gst_omx_audio_sink, GST_TYPE_AUDIO_SINK, G_IMPLEMENT_INTERFACE (GST_TYPE_STREAM_VOLUME, NULL); DEBUG_INIT); #define transform_3_4(type) \ static inline void \ transform_3_4_##type (gpointer psrc, gpointer pdst, guint len) \ { \ g##type *src = (g##type *) psrc; \ g##type *dst = (g##type *) pdst; \ for (; len > 0; len--) { \ dst[0] = src[0]; \ dst[1] = src[1]; \ dst[2] = src[2]; \ dst[3] = 0; \ src += 3; \ dst += 4; \ } \ } #define transform_5_8(type) \ static inline void \ transform_5_8_##type (gpointer psrc, gpointer pdst, guint len) \ { \ g##type *src = (g##type *) psrc; \ g##type *dst = (g##type *) pdst; \ for (; len > 0; len--) { \ dst[0] = src[0]; \ dst[1] = src[1]; \ dst[2] = src[2]; \ dst[3] = src[3]; \ dst[4] = src[4]; \ dst[5] = 0; \ dst[6] = 0; \ dst[7] = 0; \ src += 5; \ dst += 8; \ } \ } #define transform_6_8(type) \ static inline void \ transform_6_8_##type (gpointer psrc, gpointer pdst, guint len) \ { \ g##type *src = (g##type *) psrc; \ g##type *dst = (g##type *) pdst; \ for (; len > 0; len--) { \ dst[0] = src[0]; \ dst[1] = src[1]; \ dst[2] = src[2]; \ dst[3] = src[3]; \ dst[4] = src[4]; \ dst[5] = src[5]; \ dst[6] = 0; \ dst[7] = 0; \ src += 6; \ dst += 8; \ } \ } #define transform_7_8(type) \ static inline void \ transform_7_8_##type (gpointer psrc, gpointer pdst, guint len) \ { \ g##type *src = (g##type *) psrc; \ g##type *dst = (g##type *) pdst; \ for (; len > 0; len--) { \ dst[0] = src[0]; \ dst[1] = src[1]; \ dst[2] = src[2]; \ dst[3] = src[3]; \ dst[4] = src[4]; \ dst[5] = src[5]; \ dst[6] = src[6]; \ dst[7] = 0; \ src += 7; \ dst += 8; \ } \ } transform_3_4 (int16); transform_5_8 (int16); transform_6_8 (int16); transform_7_8 (int16); transform_3_4 (int32); transform_5_8 (int32); transform_6_8 (int32); transform_7_8 (int32); static void inline transform (guint in_chan, guint width, gpointer psrc, gpointer pdst, guint len) { guint out_chan = OUT_CHANNELS (in_chan); if (width == 16) { switch (out_chan) { case 4: if (in_chan == 3) { transform_3_4_int16 (psrc, pdst, len); } else { g_assert (FALSE); } break; case 8: switch (in_chan) { case 5: transform_5_8_int16 (psrc, pdst, len); break; case 6: transform_6_8_int16 (psrc, pdst, len); break; case 7: transform_7_8_int16 (psrc, pdst, len); break; default: g_assert (FALSE); } break; default: g_assert (FALSE); } } else if (width == 32) { switch (out_chan) { case 4: if (in_chan == 3) { transform_3_4_int32 (psrc, pdst, len); } else { g_assert (FALSE); } break; case 8: switch (in_chan) { case 5: transform_5_8_int32 (psrc, pdst, len); break; case 6: transform_6_8_int32 (psrc, pdst, len); break; case 7: transform_7_8_int32 (psrc, pdst, len); break; default: g_assert (FALSE); } break; default: g_assert (FALSE); } } else { g_assert (FALSE); } } static void gst_omx_audio_sink_mute_set (GstOMXAudioSink * self, gboolean mute) { if (self->comp) { OMX_ERRORTYPE err; OMX_AUDIO_CONFIG_MUTETYPE param; GST_OMX_INIT_STRUCT (¶m); param.nPortIndex = self->in_port->index; param.bMute = (mute ? OMX_TRUE : OMX_FALSE); err = gst_omx_component_set_config (self->comp, OMX_IndexConfigAudioMute, ¶m); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to set mute to %d: %s (0x%08x)", param.bMute, gst_omx_error_to_string (err), err); } } self->mute = mute; } static void gst_omx_audio_sink_volume_set (GstOMXAudioSink * self, gdouble volume) { if (self->comp) { OMX_ERRORTYPE err; OMX_AUDIO_CONFIG_VOLUMETYPE param; GST_OMX_INIT_STRUCT (¶m); param.nPortIndex = self->in_port->index; param.bLinear = OMX_TRUE; param.sVolume.nValue = volume * 100; err = gst_omx_component_set_config (self->comp, OMX_IndexConfigAudioVolume, ¶m); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to set volume to %d: %s (0x%08x)", (gint) param.sVolume.nValue, gst_omx_error_to_string (err), err); } } self->volume = volume; } static gboolean gst_omx_audio_sink_open (GstAudioSink * audiosink) { GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (audiosink); GstOMXAudioSinkClass *klass = GST_OMX_AUDIO_SINK_GET_CLASS (self); gint port_index; OMX_ERRORTYPE err; GST_DEBUG_OBJECT (self, "Opening audio sink"); self->comp = gst_omx_component_new (GST_OBJECT_CAST (self), klass->cdata.core_name, klass->cdata.component_name, klass->cdata.component_role, klass->cdata.hacks); if (!self->comp) return FALSE; if (gst_omx_component_get_state (self->comp, GST_CLOCK_TIME_NONE) != OMX_StateLoaded) return FALSE; port_index = klass->cdata.in_port_index; if (port_index == -1) { OMX_PORT_PARAM_TYPE param; GST_OMX_INIT_STRUCT (¶m); err = gst_omx_component_get_parameter (self->comp, OMX_IndexParamAudioInit, ¶m); if (err != OMX_ErrorNone) { GST_WARNING_OBJECT (self, "Couldn't get port information: %s (0x%08x)", gst_omx_error_to_string (err), err); /* Fallback */ port_index = 0; } else { GST_DEBUG_OBJECT (self, "Detected %u ports, starting at %u", (guint) param.nPorts, (guint) param.nStartPortNumber); port_index = param.nStartPortNumber + 0; } } self->in_port = gst_omx_component_add_port (self->comp, port_index); port_index = klass->cdata.out_port_index; if (port_index == -1) { OMX_PORT_PARAM_TYPE param; GST_OMX_INIT_STRUCT (¶m); err = gst_omx_component_get_parameter (self->comp, OMX_IndexParamAudioInit, ¶m); if (err != OMX_ErrorNone) { GST_WARNING_OBJECT (self, "Couldn't get port information: %s (0x%08x)", gst_omx_error_to_string (err), err); /* Fallback */ port_index = 0; } else { GST_DEBUG_OBJECT (self, "Detected %u ports, starting at %u", (guint) param.nPorts, (guint) param.nStartPortNumber); port_index = param.nStartPortNumber + 1; } } self->out_port = gst_omx_component_add_port (self->comp, port_index); if (!self->in_port || !self->out_port) return FALSE; err = gst_omx_port_set_enabled (self->in_port, FALSE); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to disable port: %s (0x%08x)", gst_omx_error_to_string (err), err); return FALSE; } err = gst_omx_port_set_enabled (self->out_port, FALSE); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to disable port: %s (0x%08x)", gst_omx_error_to_string (err), err); return FALSE; } GST_DEBUG_OBJECT (self, "Opened audio sink"); return TRUE; } static gboolean gst_omx_audio_sink_close (GstAudioSink * audiosink) { GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (audiosink); OMX_STATETYPE state; GST_DEBUG_OBJECT (self, "Closing audio sink"); state = gst_omx_component_get_state (self->comp, 0); if (state > OMX_StateLoaded || state == OMX_StateInvalid) { if (state > OMX_StateIdle) { gst_omx_component_set_state (self->comp, OMX_StateIdle); gst_omx_component_get_state (self->comp, 5 * GST_SECOND); } gst_omx_component_set_state (self->comp, OMX_StateLoaded); gst_omx_port_deallocate_buffers (self->in_port); if (state > OMX_StateLoaded) gst_omx_component_get_state (self->comp, 5 * GST_SECOND); } self->in_port = NULL; self->out_port = NULL; if (self->comp) gst_omx_component_unref (self->comp); self->comp = NULL; GST_DEBUG_OBJECT (self, "Closed audio sink"); return TRUE; } static gboolean gst_omx_audio_sink_parse_spec (GstOMXAudioSink * self, GstAudioRingBufferSpec * spec) { self->iec61937 = FALSE; self->endianness = GST_AUDIO_INFO_ENDIANNESS (&spec->info); self->rate = GST_AUDIO_INFO_RATE (&spec->info); self->channels = GST_AUDIO_INFO_CHANNELS (&spec->info); self->width = GST_AUDIO_INFO_WIDTH (&spec->info); self->is_signed = GST_AUDIO_INFO_IS_SIGNED (&spec->info); self->is_float = GST_AUDIO_INFO_IS_FLOAT (&spec->info); switch (spec->type) { case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW: { guint out_channels = OUT_CHANNELS (self->channels); self->samples = spec->segsize / self->channels / (self->width >> 3); if (self->channels == out_channels) { self->buffer_size = spec->segsize; } else { self->buffer_size = (spec->segsize / self->channels) * out_channels; } break; } case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_AC3: case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_EAC3: case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DTS: case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG: self->iec61937 = TRUE; self->endianness = G_LITTLE_ENDIAN; self->channels = 2; self->width = 16; self->is_signed = TRUE; self->is_float = FALSE; self->buffer_size = spec->segsize; break; default: return FALSE; } return TRUE; } static inline void channel_mapping (GstAudioRingBufferSpec * spec, OMX_AUDIO_CHANNELTYPE * eChannelMapping) { gint i, nchan = GST_AUDIO_INFO_CHANNELS (&spec->info); for (i = 0; i < nchan; i++) { OMX_AUDIO_CHANNELTYPE pos; switch (GST_AUDIO_INFO_POSITION (&spec->info, i)) { case GST_AUDIO_CHANNEL_POSITION_MONO: case GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER: pos = OMX_AUDIO_ChannelCF; break; case GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT: pos = OMX_AUDIO_ChannelLF; break; case GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT: pos = OMX_AUDIO_ChannelRF; break; case GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT: pos = OMX_AUDIO_ChannelLS; break; case GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT: pos = OMX_AUDIO_ChannelRS; break; case GST_AUDIO_CHANNEL_POSITION_LFE1: pos = OMX_AUDIO_ChannelLFE; break; case GST_AUDIO_CHANNEL_POSITION_REAR_CENTER: pos = OMX_AUDIO_ChannelCS; break; case GST_AUDIO_CHANNEL_POSITION_REAR_LEFT: pos = OMX_AUDIO_ChannelLR; break; case GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT: pos = OMX_AUDIO_ChannelRR; break; default: pos = OMX_AUDIO_ChannelNone; break; } eChannelMapping[i] = pos; } } static inline const gchar * ch2str (OMX_AUDIO_CHANNELTYPE ch) { switch (ch) { case OMX_AUDIO_ChannelNone: return "OMX_AUDIO_ChannelNone"; case OMX_AUDIO_ChannelLF: return "OMX_AUDIO_ChannelLF"; case OMX_AUDIO_ChannelRF: return "OMX_AUDIO_ChannelRF"; case OMX_AUDIO_ChannelCF: return "OMX_AUDIO_ChannelCF"; case OMX_AUDIO_ChannelLS: return "OMX_AUDIO_ChannelLS"; case OMX_AUDIO_ChannelRS: return "OMX_AUDIO_ChannelRS"; case OMX_AUDIO_ChannelLFE: return "OMX_AUDIO_ChannelLFE"; case OMX_AUDIO_ChannelCS: return "OMX_AUDIO_ChannelCS"; case OMX_AUDIO_ChannelLR: return "OMX_AUDIO_ChannelLR"; case OMX_AUDIO_ChannelRR: return "OMX_AUDIO_ChannelRR"; default: return "Invalid value"; } } static inline gboolean gst_omx_audio_sink_configure_pcm (GstOMXAudioSink * self, GstAudioRingBufferSpec * spec) { OMX_AUDIO_PARAM_PCMMODETYPE param; OMX_ERRORTYPE err; GST_OMX_INIT_STRUCT (¶m); param.nPortIndex = self->in_port->index; param.nChannels = OUT_CHANNELS (self->channels); param.eNumData = (self->is_signed ? OMX_NumericalDataSigned : OMX_NumericalDataUnsigned); param.eEndian = ((self->endianness == G_LITTLE_ENDIAN) ? OMX_EndianLittle : OMX_EndianBig); param.bInterleaved = OMX_TRUE; param.nBitPerSample = self->width; param.nSamplingRate = self->rate; if (self->is_float) { /* This is cherrypicked from xbmc but it doesn't seems to be valid on my RPI. * https://github.com/xbmc/xbmc/blob/master/xbmc/cores/AudioEngine/Sinks/AESinkPi.cpp */ param.ePCMMode = (OMX_AUDIO_PCMMODETYPE) 0x8000; } else { param.ePCMMode = OMX_AUDIO_PCMModeLinear; } if (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW) { channel_mapping (spec, ¶m.eChannelMapping[0]); } GST_DEBUG_OBJECT (self, "Setting PCM parameters"); GST_DEBUG_OBJECT (self, " nChannels: %u", (guint) param.nChannels); GST_DEBUG_OBJECT (self, " eNumData: %s", (param.eNumData == OMX_NumericalDataSigned ? "signed" : "unsigned")); GST_DEBUG_OBJECT (self, " eEndian: %s", (param.eEndian == OMX_EndianLittle ? "little endian" : "big endian")); GST_DEBUG_OBJECT (self, " bInterleaved: %d", param.bInterleaved); GST_DEBUG_OBJECT (self, " nBitPerSample: %u", (guint) param.nBitPerSample); GST_DEBUG_OBJECT (self, " nSamplingRate: %u", (guint) param.nSamplingRate); GST_DEBUG_OBJECT (self, " ePCMMode: %04x", param.ePCMMode); GST_DEBUG_OBJECT (self, " eChannelMapping: {%s, %s, %s, %s, %s, %s, %s, %s}", ch2str (param.eChannelMapping[0]), ch2str (param.eChannelMapping[1]), ch2str (param.eChannelMapping[2]), ch2str (param.eChannelMapping[3]), ch2str (param.eChannelMapping[4]), ch2str (param.eChannelMapping[5]), ch2str (param.eChannelMapping[6]), ch2str (param.eChannelMapping[7])); err = gst_omx_component_set_parameter (self->comp, OMX_IndexParamAudioPcm, ¶m); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to set PCM parameters: %s (0x%08x)", gst_omx_error_to_string (err), err); return FALSE; } return TRUE; } static gboolean gst_omx_audio_sink_prepare (GstAudioSink * audiosink, GstAudioRingBufferSpec * spec) { GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (audiosink); OMX_PARAM_PORTDEFINITIONTYPE port_def; OMX_ERRORTYPE err; if (!gst_omx_audio_sink_parse_spec (self, spec)) goto spec_parse; gst_omx_port_get_port_definition (self->in_port, &port_def); port_def.nBufferSize = self->buffer_size; /* Only allocate a min number of buffers for transfers from our ringbuffer to * the hw ringbuffer as we want to keep our small */ port_def.nBufferCountActual = MAX (port_def.nBufferCountMin, 2); port_def.format.audio.eEncoding = OMX_AUDIO_CodingPCM; GST_DEBUG_OBJECT (self, "Updating outport port definition"); GST_DEBUG_OBJECT (self, " nBufferSize: %u", (guint) port_def.nBufferSize); GST_DEBUG_OBJECT (self, " nBufferCountActual: %u", (guint) port_def.nBufferCountActual); GST_DEBUG_OBJECT (self, " audio.eEncoding: 0x%08x", port_def.format.audio.eEncoding); err = gst_omx_port_update_port_definition (self->in_port, &port_def); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to configure port: %s (0x%08x)", gst_omx_error_to_string (err), err); goto configuration; } if (!gst_omx_audio_sink_configure_pcm (self, spec)) { goto configuration; } err = gst_omx_component_set_state (self->comp, OMX_StateIdle); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to set state idle: %s (0x%08x)", gst_omx_error_to_string (err), err); goto activation; } err = gst_omx_port_set_flushing (self->in_port, 5 * GST_SECOND, FALSE); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to set port not flushing: %s (0x%08x)", gst_omx_error_to_string (err), err); goto activation; } err = gst_omx_port_set_enabled (self->in_port, TRUE); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to enable port: %s (0x%08x)", gst_omx_error_to_string (err), err); goto activation; } GST_DEBUG_OBJECT (self, "Allocate buffers"); err = gst_omx_port_allocate_buffers (self->in_port); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed on buffer allocation: %s (0x%08x)", gst_omx_error_to_string (err), err); goto activation; } err = gst_omx_port_wait_enabled (self->in_port, 5 * GST_SECOND); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "port not enabled: %s (0x%08x)", gst_omx_error_to_string (err), err); goto activation; } err = gst_omx_port_mark_reconfigured (self->in_port); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Couln't mark port as reconfigured: %s (0x%08x)", gst_omx_error_to_string (err), err); goto activation; } err = gst_omx_component_set_state (self->comp, OMX_StatePause); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to set state paused: %s (0x%08x)", gst_omx_error_to_string (err), err); goto activation; } if (gst_omx_component_get_state (self->comp, GST_CLOCK_TIME_NONE) != OMX_StatePause) goto activation; /* Configure some parameters */ GST_OBJECT_LOCK (self); gst_omx_audio_sink_mute_set (self, self->mute); gst_omx_audio_sink_volume_set (self, self->volume); GST_OBJECT_UNLOCK (self); #if defined (USE_OMX_TARGET_RPI) { GstOMXAudioSinkClass *klass = GST_OMX_AUDIO_SINK_GET_CLASS (self); OMX_ERRORTYPE err; OMX_CONFIG_BRCMAUDIODESTINATIONTYPE param; if (klass->destination && strlen (klass->destination) < sizeof (param.sName)) { GST_DEBUG_OBJECT (self, "Setting destination: %s", klass->destination); GST_OMX_INIT_STRUCT (¶m); strcpy ((char *) param.sName, klass->destination); err = gst_omx_component_set_config (self->comp, OMX_IndexConfigBrcmAudioDestination, ¶m); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to configuring destination: %s (0x%08x)", gst_omx_error_to_string (err), err); goto activation; } } } #endif return TRUE; /* ERRORS */ spec_parse: { GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS, (NULL), ("Error parsing spec")); return FALSE; } configuration: { GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS, (NULL), ("Configuration failed")); return FALSE; } activation: { GST_ELEMENT_ERROR (self, RESOURCE, SETTINGS, (NULL), ("Component activation failed")); return FALSE; } } static gboolean gst_omx_audio_sink_unprepare (GstAudioSink * audiosink) { GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (audiosink); OMX_ERRORTYPE err; if (gst_omx_component_get_state (self->comp, 0) == OMX_StateIdle) return TRUE; err = gst_omx_port_set_flushing (self->in_port, 5 * GST_SECOND, TRUE); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to set port flushing: %s (0x%08x)", gst_omx_error_to_string (err), err); goto failed; } err = gst_omx_component_set_state (self->comp, OMX_StateIdle); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to set state idle: %s (0x%08x)", gst_omx_error_to_string (err), err); goto failed; } err = gst_omx_port_set_enabled (self->in_port, FALSE); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to set port disabled: %s (0x%08x)", gst_omx_error_to_string (err), err); goto failed; } err = gst_omx_port_wait_buffers_released (self->in_port, 5 * GST_SECOND); if (err != OMX_ErrorNone) { goto failed; } err = gst_omx_port_deallocate_buffers (self->in_port); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Couldn't deallocate buffers: %s (0x%08x)", gst_omx_error_to_string (err), err); goto failed; } err = gst_omx_port_wait_enabled (self->in_port, 1 * GST_SECOND); if (err != OMX_ErrorNone) { goto failed; } gst_omx_component_get_state (self->comp, GST_CLOCK_TIME_NONE); return TRUE; /* ERRORS */ failed: { GST_ELEMENT_ERROR (self, LIBRARY, FAILED, (NULL), ("OpenMAX component in error state %s (0x%08x)", gst_omx_component_get_last_error_string (self->comp), gst_omx_component_get_last_error (self->comp))); return FALSE; } } static GstOMXBuffer * gst_omx_audio_sink_acquire_buffer (GstOMXAudioSink * self) { GstOMXAcquireBufferReturn acq_ret = GST_OMX_ACQUIRE_BUFFER_ERROR; GstOMXPort *port = self->in_port; OMX_ERRORTYPE err; GstOMXBuffer *buf = NULL; while (!buf) { acq_ret = gst_omx_port_acquire_buffer (port, &buf, GST_OMX_WAIT); if (acq_ret == GST_OMX_ACQUIRE_BUFFER_ERROR) { goto component_error; } else if (acq_ret == GST_OMX_ACQUIRE_BUFFER_FLUSHING) { GST_DEBUG_OBJECT (self, "Flushing..."); goto flushing; } else if (acq_ret == GST_OMX_ACQUIRE_BUFFER_RECONFIGURE) { GST_DEBUG_OBJECT (self, "Reconfigure..."); /* Reallocate all buffers */ err = gst_omx_port_set_enabled (port, FALSE); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to set port disabled: %s (0x%08x)", gst_omx_error_to_string (err), err); goto reconfigure_error; } err = gst_omx_port_wait_buffers_released (port, 5 * GST_SECOND); if (err != OMX_ErrorNone) { goto reconfigure_error; } err = gst_omx_port_deallocate_buffers (port); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Couldn't deallocate buffers: %s (0x%08x)", gst_omx_error_to_string (err), err); goto reconfigure_error; } err = gst_omx_port_wait_enabled (port, 1 * GST_SECOND); if (err != OMX_ErrorNone) { goto reconfigure_error; } err = gst_omx_port_set_enabled (port, TRUE); if (err != OMX_ErrorNone) { goto reconfigure_error; } err = gst_omx_port_allocate_buffers (port); if (err != OMX_ErrorNone) { goto reconfigure_error; } err = gst_omx_port_wait_enabled (port, 5 * GST_SECOND); if (err != OMX_ErrorNone) { goto reconfigure_error; } err = gst_omx_port_mark_reconfigured (port); if (err != OMX_ErrorNone) { goto reconfigure_error; } continue; } } return buf; /* ERRORS */ component_error: { GST_ELEMENT_ERROR (self, LIBRARY, FAILED, (NULL), ("OpenMAX component in error state %s (0x%08x)", gst_omx_component_get_last_error_string (self->comp), gst_omx_component_get_last_error (self->comp))); return NULL; } reconfigure_error: { GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, (NULL), ("Unable to reconfigure input port")); return NULL; } flushing: { return NULL; } } static gint gst_omx_audio_sink_write (GstAudioSink * audiosink, gpointer data, guint length) { GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (audiosink); GstOMXBuffer *buf; OMX_ERRORTYPE err; GST_LOG_OBJECT (self, "received audio samples buffer of %u bytes", length); GST_OMX_AUDIO_SINK_LOCK (self); if (!(buf = gst_omx_audio_sink_acquire_buffer (self))) { goto beach; } if (buf->omx_buf->nAllocLen == length) { memcpy (buf->omx_buf->pBuffer + buf->omx_buf->nOffset, data, length); } else { transform (self->channels, self->width, data, buf->omx_buf->pBuffer + buf->omx_buf->nOffset, self->samples); } buf->omx_buf->nFilledLen = buf->omx_buf->nAllocLen; err = gst_omx_port_release_buffer (self->in_port, buf); if (err != OMX_ErrorNone) goto release_error; beach: GST_OMX_AUDIO_SINK_UNLOCK (self); return length; /* ERRORS */ release_error: { GST_OMX_AUDIO_SINK_UNLOCK (self); GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, (NULL), ("Failed to relase input buffer to component: %s (0x%08x)", gst_omx_error_to_string (err), err)); return 0; } } static guint gst_omx_audio_sink_delay (GstAudioSink * audiosink) { #if defined (USE_OMX_TARGET_RPI) GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (audiosink); OMX_PARAM_U32TYPE param; OMX_ERRORTYPE err; GST_OMX_INIT_STRUCT (¶m); param.nPortIndex = self->in_port->index; param.nU32 = 0; err = gst_omx_component_get_config (self->comp, OMX_IndexConfigAudioRenderingLatency, ¶m); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to get rendering latency: %s (0x%08x)", gst_omx_error_to_string (err), err); param.nU32 = 0; } GST_DEBUG_OBJECT (self, "reported delay %u samples", (guint) param.nU32); return param.nU32; #else return 0; #endif } static void gst_omx_audio_sink_reset (GstAudioSink * audiosink) { GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (audiosink); OMX_STATETYPE state; GST_DEBUG_OBJECT (self, "Flushing sink"); gst_omx_port_set_flushing (self->in_port, 5 * GST_SECOND, TRUE); GST_OMX_AUDIO_SINK_LOCK (self); if ((state = gst_omx_component_get_state (self->comp, 0)) > OMX_StatePause) { gst_omx_component_set_state (self->comp, OMX_StatePause); gst_omx_component_get_state (self->comp, GST_CLOCK_TIME_NONE); } gst_omx_component_set_state (self->comp, state); gst_omx_component_get_state (self->comp, GST_CLOCK_TIME_NONE); gst_omx_port_set_flushing (self->in_port, 5 * GST_SECOND, FALSE); GST_OMX_AUDIO_SINK_UNLOCK (self); } static GstBuffer * gst_omx_audio_sink_payload (GstAudioBaseSink * audiobasesink, GstBuffer * buf) { GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (audiobasesink); if (self->iec61937) { GstBuffer *out; gint framesize; GstMapInfo iinfo, oinfo; GstAudioRingBufferSpec *spec = &audiobasesink->ringbuffer->spec; framesize = gst_audio_iec61937_frame_size (spec); if (framesize <= 0) return NULL; out = gst_buffer_new_and_alloc (framesize); gst_buffer_map (buf, &iinfo, GST_MAP_READ); gst_buffer_map (out, &oinfo, GST_MAP_WRITE); if (!gst_audio_iec61937_payload (iinfo.data, iinfo.size, oinfo.data, oinfo.size, spec, G_BIG_ENDIAN)) { gst_buffer_unref (out); return NULL; } gst_buffer_unmap (buf, &iinfo); gst_buffer_unmap (out, &oinfo); gst_buffer_copy_into (out, buf, GST_BUFFER_COPY_METADATA, 0, -1); return out; } return gst_buffer_ref (buf); } static gboolean gst_omx_audio_sink_accept_caps (GstOMXAudioSink * self, GstCaps * caps) { GstPad *pad = GST_BASE_SINK (self)->sinkpad; GstCaps *pad_caps; GstStructure *st; gboolean ret = FALSE; GstAudioRingBufferSpec spec = { 0 }; pad_caps = gst_pad_query_caps (pad, caps); if (!pad_caps || gst_caps_is_empty (pad_caps)) { if (pad_caps) gst_caps_unref (pad_caps); ret = FALSE; goto done; } gst_caps_unref (pad_caps); /* If we've not got fixed caps, creating a stream might fail, so let's just * return from here with default acceptcaps behaviour */ if (!gst_caps_is_fixed (caps)) goto done; /* parse helper expects this set, so avoid nasty warning * will be set properly later on anyway */ spec.latency_time = GST_SECOND; if (!gst_audio_ring_buffer_parse_caps (&spec, caps)) goto done; /* Make sure input is framed (one frame per buffer) and can be payloaded */ switch (spec.type) { case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_AC3: case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_EAC3: case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DTS: case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG: { gboolean framed = FALSE, parsed = FALSE; st = gst_caps_get_structure (caps, 0); gst_structure_get_boolean (st, "framed", &framed); gst_structure_get_boolean (st, "parsed", &parsed); if ((!framed && !parsed) || gst_audio_iec61937_frame_size (&spec) <= 0) goto done; } default:{ } } ret = TRUE; done: gst_caps_replace (&spec.caps, NULL); return ret; } static gboolean gst_omx_audio_sink_query (GstBaseSink * basesink, GstQuery * query) { GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (basesink); gboolean ret; switch (GST_QUERY_TYPE (query)) { case GST_QUERY_ACCEPT_CAPS: { GstCaps *caps; gst_query_parse_accept_caps (query, &caps); ret = gst_omx_audio_sink_accept_caps (self, caps); gst_query_set_accept_caps_result (query, ret); ret = TRUE; break; } default: ret = GST_BASE_SINK_CLASS (parent_class)->query (basesink, query); break; } return ret; } static void gst_omx_audio_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (object); switch (prop_id) { case PROP_MUTE: { gboolean mute = g_value_get_boolean (value); GST_OBJECT_LOCK (self); if (self->mute != mute) { gst_omx_audio_sink_mute_set (self, mute); } GST_OBJECT_UNLOCK (self); break; } case PROP_VOLUME: { gdouble volume = g_value_get_double (value); GST_OBJECT_LOCK (self); if (volume != self->volume) { gst_omx_audio_sink_volume_set (self, volume); } GST_OBJECT_UNLOCK (self); break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_omx_audio_sink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (object); switch (prop_id) { case PROP_MUTE: GST_OBJECT_LOCK (self); g_value_set_boolean (value, self->mute); GST_OBJECT_UNLOCK (self); break; case PROP_VOLUME: GST_OBJECT_LOCK (self); g_value_set_double (value, self->volume); GST_OBJECT_UNLOCK (self); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GstStateChangeReturn gst_omx_audio_sink_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (element); OMX_ERRORTYPE err; switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_PLAYING: { GST_DEBUG_OBJECT (self, "going to PLAYING state"); err = gst_omx_component_set_state (self->comp, OMX_StateExecuting); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to set state executing: %s (0x%08x)", gst_omx_error_to_string (err), err); return GST_STATE_CHANGE_FAILURE; } if (gst_omx_component_get_state (self->comp, GST_CLOCK_TIME_NONE) != OMX_StateExecuting) { return GST_STATE_CHANGE_FAILURE; } GST_DEBUG_OBJECT (self, "in PLAYING state"); break; } default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); switch (transition) { case GST_STATE_CHANGE_PLAYING_TO_PAUSED: { GST_DEBUG_OBJECT (self, "going to PAUSED state"); err = gst_omx_component_set_state (self->comp, OMX_StatePause); if (err != OMX_ErrorNone) { GST_ERROR_OBJECT (self, "Failed to set state paused: %s (0x%08x)", gst_omx_error_to_string (err), err); return GST_STATE_CHANGE_FAILURE; } if (gst_omx_component_get_state (self->comp, GST_CLOCK_TIME_NONE) != OMX_StatePause) { return GST_STATE_CHANGE_FAILURE; } GST_DEBUG_OBJECT (self, "in PAUSED state"); break; } default: break; } return ret; } static void gst_omx_audio_sink_finalize (GObject * object) { GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (object); g_mutex_clear (&self->lock); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_omx_audio_sink_class_init (GstOMXAudioSinkClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstElementClass *element_class = GST_ELEMENT_CLASS (klass); GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS (klass); GstAudioBaseSinkClass *baudiosink_class = GST_AUDIO_BASE_SINK_CLASS (klass); GstAudioSinkClass *audiosink_class = GST_AUDIO_SINK_CLASS (klass); gobject_class->set_property = gst_omx_audio_sink_set_property; gobject_class->get_property = gst_omx_audio_sink_get_property; gobject_class->finalize = gst_omx_audio_sink_finalize; g_object_class_install_property (gobject_class, PROP_MUTE, g_param_spec_boolean ("mute", "Mute", "mute channel", DEFAULT_PROP_MUTE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_VOLUME, g_param_spec_double ("volume", "Volume", "volume factor, 1.0=100%", 0.0, VOLUME_MAX_DOUBLE, DEFAULT_PROP_VOLUME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); element_class->change_state = GST_DEBUG_FUNCPTR (gst_omx_audio_sink_change_state); basesink_class->query = GST_DEBUG_FUNCPTR (gst_omx_audio_sink_query); baudiosink_class->payload = GST_DEBUG_FUNCPTR (gst_omx_audio_sink_payload); audiosink_class->open = GST_DEBUG_FUNCPTR (gst_omx_audio_sink_open); audiosink_class->close = GST_DEBUG_FUNCPTR (gst_omx_audio_sink_close); audiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_omx_audio_sink_prepare); audiosink_class->unprepare = GST_DEBUG_FUNCPTR (gst_omx_audio_sink_unprepare); audiosink_class->write = GST_DEBUG_FUNCPTR (gst_omx_audio_sink_write); audiosink_class->delay = GST_DEBUG_FUNCPTR (gst_omx_audio_sink_delay); audiosink_class->reset = GST_DEBUG_FUNCPTR (gst_omx_audio_sink_reset); klass->cdata.type = GST_OMX_COMPONENT_TYPE_SINK; } static void gst_omx_audio_sink_init (GstOMXAudioSink * self) { g_mutex_init (&self->lock); self->mute = DEFAULT_PROP_MUTE; self->volume = DEFAULT_PROP_VOLUME; /* For the Raspberry PI there's a big hw buffer and 400 ms seems a good * size for our ringbuffer. OpenSL ES Sink also allocates a buffer of 400 ms * in Android so I guess that this should be a sane value for OpenMax in * general. */ GST_AUDIO_BASE_SINK (self)->buffer_time = 400000; gst_audio_base_sink_set_provide_clock (GST_AUDIO_BASE_SINK (self), TRUE); }