gstreamer/subprojects/gst-omx/omx/gstomxaudiosink.c

1229 lines
35 KiB
C

/*
* 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)",
(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 (&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 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 (&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: %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,
&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: %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 (&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;
}
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 (&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 %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);
}