summaryrefslogtreecommitdiff
path: root/omx/gstomxaudiosink.c
diff options
context:
space:
mode:
Diffstat (limited to 'omx/gstomxaudiosink.c')
-rw-r--r--omx/gstomxaudiosink.c1224
1 files changed, 1224 insertions, 0 deletions
diff --git a/omx/gstomxaudiosink.c b/omx/gstomxaudiosink.c
new file mode 100644
index 0000000..f933077
--- /dev/null
+++ b/omx/gstomxaudiosink.c
@@ -0,0 +1,1224 @@
+/*
+ * Copyright (C) 2014, Fluendo, S.A.
+ * Copyright (C) 2014, Metrological Media Innovations B.V.
+ * Author: Josep Torra <josep@fluendo.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#include <gst/gst.h>
+#include <gst/audio/audio.h>
+
+#include <math.h>
+
+#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 (&param);
+ param.nPortIndex = self->in_port->index;
+ param.bMute = (mute ? OMX_TRUE : OMX_FALSE);
+ err = gst_omx_component_set_config (self->comp,
+ OMX_IndexConfigAudioMute, &param);
+ 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 (&param);
+ 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, &param);
+ if (err != OMX_ErrorNone) {
+ GST_ERROR_OBJECT (self, "Failed to set volume to %d: %s (0x%08x)",
+ 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 (&param);
+
+ err =
+ gst_omx_component_get_parameter (self->comp, OMX_IndexParamAudioInit,
+ &param);
+ 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 (&param);
+
+ err =
+ gst_omx_component_get_parameter (self->comp, OMX_IndexParamAudioInit,
+ &param);
+ 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 enable 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 enable 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_free (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 (&param);
+ 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, &param.eChannelMapping[0]);
+ }
+
+ GST_DEBUG_OBJECT (self, "Setting PCM parameters");
+ GST_DEBUG_OBJECT (self, " nChannels: %d", 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: %d", param.nBitPerSample);
+ GST_DEBUG_OBJECT (self, " nSamplingRate: %d", 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,
+ &param);
+ 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: %d", port_def.nBufferSize);
+ GST_DEBUG_OBJECT (self, " nBufferCountActual: %d",
+ 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_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 (&param);
+ strcpy ((char *) param.sName, klass->destination);
+ err = gst_omx_component_set_config (self->comp,
+ OMX_IndexConfigBrcmAudioDestination, &param);
+ 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;
+ }
+
+ 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 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);
+ 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)
+{
+ GstOMXAudioSink *self = GST_OMX_AUDIO_SINK (audiosink);
+ OMX_PARAM_U32TYPE param;
+ OMX_ERRORTYPE err;
+
+ GST_OMX_INIT_STRUCT (&param);
+ param.nPortIndex = self->in_port->index;
+ param.nU32 = 0;
+ err = gst_omx_component_get_config (self->comp,
+ OMX_IndexConfigAudioRenderingLatency, &param);
+ 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 %d samples", param.nU32);
+ return param.nU32;
+}
+
+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);
+}