diff options
author | Sebastian Dröge <sebastian@centricular.com> | 2014-05-10 23:12:54 +0200 |
---|---|---|
committer | Sebastian Dröge <sebastian@centricular.com> | 2014-07-02 09:21:00 +0200 |
commit | 0c3b3ef3d091fdd67a16c0dc2dcddb5f564ee3e4 (patch) | |
tree | d0644ee15498dfa44e262e204a3123d1b5a5925d | |
parent | 3d94d3e44d7576a43c8755b2f7b5cf35eb83e8a6 (diff) | |
download | gst-omx-0c3b3ef3d091fdd67a16c0dc2dcddb5f564ee3e4.tar.gz |
omx: Add audio decoder base class and a subclass for MP3
-rw-r--r-- | omx/Makefile.am | 4 | ||||
-rw-r--r-- | omx/gstomx.c | 5 | ||||
-rw-r--r-- | omx/gstomxaudiodec.c | 1299 | ||||
-rw-r--r-- | omx/gstomxaudiodec.h | 100 | ||||
-rw-r--r-- | omx/gstomxmp3dec.c | 209 | ||||
-rw-r--r-- | omx/gstomxmp3dec.h | 60 |
6 files changed, 1676 insertions, 1 deletions
diff --git a/omx/Makefile.am b/omx/Makefile.am index 5d90520..203d28f 100644 --- a/omx/Makefile.am +++ b/omx/Makefile.am @@ -16,6 +16,7 @@ libgstomx_la_SOURCES = \ gstomxvideo.c \ gstomxvideodec.c \ gstomxvideoenc.c \ + gstomxaudiodec.c \ gstomxaudioenc.c \ gstomxmjpegdec.c \ gstomxmpeg4videodec.c \ @@ -28,6 +29,7 @@ libgstomx_la_SOURCES = \ gstomxmpeg4videoenc.c \ gstomxh264enc.c \ gstomxh263enc.c \ + gstomxmp3dec.c \ gstomxaacenc.c \ gstomxaudiosink.c \ gstomxanalogaudiosink.c \ @@ -38,6 +40,7 @@ noinst_HEADERS = \ gstomxvideo.h \ gstomxvideodec.h \ gstomxvideoenc.h \ + gstomxaudiodec.h \ gstomxaudioenc.h \ gstomxmjpegdec.h \ gstomxmpeg2videodec.h \ @@ -50,6 +53,7 @@ noinst_HEADERS = \ gstomxmpeg4videoenc.h \ gstomxh264enc.h \ gstomxh263enc.h \ + gstomxmp3dec.h \ gstomxaacenc.h \ gstomxaudiosink.h \ gstomxanalogaudiosink.h \ diff --git a/omx/gstomx.c b/omx/gstomx.c index 7766eab..d611de3 100644 --- a/omx/gstomx.c +++ b/omx/gstomx.c @@ -39,6 +39,7 @@ #include "gstomxmpeg4videoenc.h" #include "gstomxh264enc.h" #include "gstomxh263enc.h" +#include "gstomxmp3dec.h" #include "gstomxaacenc.h" #include "gstomxanalogaudiosink.h" #include "gstomxhdmiaudiosink.h" @@ -2244,7 +2245,8 @@ static const GGetTypeFunction types[] = { gst_omx_h264_dec_get_type, gst_omx_h263_dec_get_type, gst_omx_wmv_dec_get_type, gst_omx_mpeg4_video_enc_get_type, gst_omx_h264_enc_get_type, gst_omx_h263_enc_get_type, - gst_omx_aac_enc_get_type, gst_omx_mjpeg_dec_get_type + gst_omx_aac_enc_get_type, gst_omx_mjpeg_dec_get_type, + gst_omx_mp3_dec_get_type #ifdef HAVE_VP8 , gst_omx_vp8_dec_get_type #endif @@ -2263,6 +2265,7 @@ static const struct TypeOffest base_types[] = { {gst_omx_audio_sink_get_type, G_STRUCT_OFFSET (GstOMXAudioSinkClass, cdata)}, {gst_omx_video_dec_get_type, G_STRUCT_OFFSET (GstOMXVideoDecClass, cdata)}, {gst_omx_video_enc_get_type, G_STRUCT_OFFSET (GstOMXVideoEncClass, cdata)}, + {gst_omx_audio_dec_get_type, G_STRUCT_OFFSET (GstOMXAudioDecClass, cdata)}, {gst_omx_audio_enc_get_type, G_STRUCT_OFFSET (GstOMXAudioEncClass, cdata)}, }; diff --git a/omx/gstomxaudiodec.c b/omx/gstomxaudiodec.c new file mode 100644 index 0000000..d155017 --- /dev/null +++ b/omx/gstomxaudiodec.c @@ -0,0 +1,1299 @@ +/* + * Copyright (C) 2011, Hewlett-Packard Development Company, L.P. + * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, Collabora Ltd. + * Copyright (C) 2013, Collabora Ltd. + * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk> + * Copyright (C) 2014, Sebastian Dröge <sebastian@centricular.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 <string.h> + +#include "gstomxaudiodec.h" + +GST_DEBUG_CATEGORY_STATIC (gst_omx_audio_dec_debug_category); +#define GST_CAT_DEFAULT gst_omx_audio_dec_debug_category + +/* prototypes */ +static void gst_omx_audio_dec_finalize (GObject * object); + +static GstStateChangeReturn +gst_omx_audio_dec_change_state (GstElement * element, + GstStateChange transition); + +static gboolean gst_omx_audio_dec_open (GstAudioDecoder * decoder); +static gboolean gst_omx_audio_dec_close (GstAudioDecoder * decoder); +static gboolean gst_omx_audio_dec_start (GstAudioDecoder * decoder); +static gboolean gst_omx_audio_dec_stop (GstAudioDecoder * decoder); +static gboolean gst_omx_audio_dec_set_format (GstAudioDecoder * decoder, + GstCaps * caps); +static void gst_omx_audio_dec_flush (GstAudioDecoder * decoder, gboolean hard); +static GstFlowReturn gst_omx_audio_dec_handle_frame (GstAudioDecoder * decoder, + GstBuffer * buffer); +static GstFlowReturn gst_omx_audio_dec_drain (GstOMXAudioDec * self, + gboolean is_eos); + +enum +{ + PROP_0 +}; + +/* class initialization */ + +#define DEBUG_INIT \ + GST_DEBUG_CATEGORY_INIT (gst_omx_audio_dec_debug_category, "omxaudiodec", 0, \ + "debug category for gst-omx audio decoder base class"); + + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstOMXAudioDec, gst_omx_audio_dec, + GST_TYPE_AUDIO_DECODER, DEBUG_INIT); + +static void +gst_omx_audio_dec_class_init (GstOMXAudioDecClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstAudioDecoderClass *audio_decoder_class = GST_AUDIO_DECODER_CLASS (klass); + + gobject_class->finalize = gst_omx_audio_dec_finalize; + + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_omx_audio_dec_change_state); + + audio_decoder_class->open = GST_DEBUG_FUNCPTR (gst_omx_audio_dec_open); + audio_decoder_class->close = GST_DEBUG_FUNCPTR (gst_omx_audio_dec_close); + audio_decoder_class->start = GST_DEBUG_FUNCPTR (gst_omx_audio_dec_start); + audio_decoder_class->stop = GST_DEBUG_FUNCPTR (gst_omx_audio_dec_stop); + audio_decoder_class->flush = GST_DEBUG_FUNCPTR (gst_omx_audio_dec_flush); + audio_decoder_class->set_format = + GST_DEBUG_FUNCPTR (gst_omx_audio_dec_set_format); + audio_decoder_class->handle_frame = + GST_DEBUG_FUNCPTR (gst_omx_audio_dec_handle_frame); + + klass->cdata.type = GST_OMX_COMPONENT_TYPE_FILTER; + klass->cdata.default_src_template_caps = + "audio/x-raw, " + "rate = (int) [ 1, MAX ], " + "channels = (int) [ 1, " G_STRINGIFY (OMX_AUDIO_MAXCHANNELS) " ], " + "format = (string) " GST_AUDIO_FORMATS_ALL; +} + +static void +gst_omx_audio_dec_init (GstOMXAudioDec * self) +{ + gst_audio_decoder_set_needs_format (GST_AUDIO_DECODER (self), TRUE); + gst_audio_decoder_set_drainable (GST_AUDIO_DECODER (self), TRUE); + + g_mutex_init (&self->drain_lock); + g_cond_init (&self->drain_cond); +} + +static gboolean +gst_omx_audio_dec_open (GstAudioDecoder * decoder) +{ + GstOMXAudioDec *self = GST_OMX_AUDIO_DEC (decoder); + GstOMXAudioDecClass *klass = GST_OMX_AUDIO_DEC_GET_CLASS (self); + gint in_port_index, out_port_index; + + GST_DEBUG_OBJECT (self, "Opening decoder"); + + self->dec = + gst_omx_component_new (GST_OBJECT_CAST (self), klass->cdata.core_name, + klass->cdata.component_name, klass->cdata.component_role, + klass->cdata.hacks); + self->started = FALSE; + + if (!self->dec) + return FALSE; + + if (gst_omx_component_get_state (self->dec, + GST_CLOCK_TIME_NONE) != OMX_StateLoaded) + return FALSE; + + in_port_index = klass->cdata.in_port_index; + out_port_index = klass->cdata.out_port_index; + + if (in_port_index == -1 || out_port_index == -1) { + OMX_PORT_PARAM_TYPE param; + OMX_ERRORTYPE err; + + GST_OMX_INIT_STRUCT (¶m); + + err = + gst_omx_component_get_parameter (self->dec, 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 */ + in_port_index = 0; + out_port_index = 1; + } else { + GST_DEBUG_OBJECT (self, "Detected %u ports, starting at %u", + (guint) param.nPorts, (guint) param.nStartPortNumber); + in_port_index = param.nStartPortNumber + 0; + out_port_index = param.nStartPortNumber + 1; + } + } + self->dec_in_port = gst_omx_component_add_port (self->dec, in_port_index); + self->dec_out_port = gst_omx_component_add_port (self->dec, out_port_index); + + if (!self->dec_in_port || !self->dec_out_port) + return FALSE; + + GST_DEBUG_OBJECT (self, "Opened decoder"); + + return TRUE; +} + +static gboolean +gst_omx_audio_dec_shutdown (GstOMXAudioDec * self) +{ + OMX_STATETYPE state; + + GST_DEBUG_OBJECT (self, "Shutting down decoder"); + + state = gst_omx_component_get_state (self->dec, 0); + if (state > OMX_StateLoaded || state == OMX_StateInvalid) { + if (state > OMX_StateIdle) { + gst_omx_component_set_state (self->dec, OMX_StateIdle); + gst_omx_component_get_state (self->dec, 5 * GST_SECOND); + } + gst_omx_component_set_state (self->dec, OMX_StateLoaded); + gst_omx_port_deallocate_buffers (self->dec_in_port); + gst_omx_port_deallocate_buffers (self->dec_out_port); + if (state > OMX_StateLoaded) + gst_omx_component_get_state (self->dec, 5 * GST_SECOND); + } + + return TRUE; +} + +static gboolean +gst_omx_audio_dec_close (GstAudioDecoder * decoder) +{ + GstOMXAudioDec *self = GST_OMX_AUDIO_DEC (decoder); + + GST_DEBUG_OBJECT (self, "Closing decoder"); + + if (!gst_omx_audio_dec_shutdown (self)) + return FALSE; + + self->dec_in_port = NULL; + self->dec_out_port = NULL; + if (self->dec) + gst_omx_component_free (self->dec); + self->dec = NULL; + + self->started = FALSE; + + GST_DEBUG_OBJECT (self, "Closed decoder"); + + return TRUE; +} + +static void +gst_omx_audio_dec_finalize (GObject * object) +{ + GstOMXAudioDec *self = GST_OMX_AUDIO_DEC (object); + + g_mutex_clear (&self->drain_lock); + g_cond_clear (&self->drain_cond); + + G_OBJECT_CLASS (gst_omx_audio_dec_parent_class)->finalize (object); +} + +static GstStateChangeReturn +gst_omx_audio_dec_change_state (GstElement * element, GstStateChange transition) +{ + GstOMXAudioDec *self; + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + g_return_val_if_fail (GST_IS_OMX_AUDIO_DEC (element), + GST_STATE_CHANGE_FAILURE); + self = GST_OMX_AUDIO_DEC (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + self->downstream_flow_ret = GST_FLOW_OK; + self->draining = FALSE; + self->started = FALSE; + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + if (self->dec_in_port) + gst_omx_port_set_flushing (self->dec_in_port, 5 * GST_SECOND, TRUE); + if (self->dec_out_port) + gst_omx_port_set_flushing (self->dec_out_port, 5 * GST_SECOND, TRUE); + + g_mutex_lock (&self->drain_lock); + self->draining = FALSE; + g_cond_broadcast (&self->drain_cond); + g_mutex_unlock (&self->drain_lock); + break; + default: + break; + } + + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + ret = + GST_ELEMENT_CLASS (gst_omx_audio_dec_parent_class)->change_state + (element, transition); + + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + self->downstream_flow_ret = GST_FLOW_FLUSHING; + self->started = FALSE; + + if (!gst_omx_audio_dec_shutdown (self)) + ret = GST_STATE_CHANGE_FAILURE; + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + return ret; +} + +static void +gst_omx_audio_dec_loop (GstOMXAudioDec * self) +{ + GstOMXPort *port = self->dec_out_port; + GstOMXBuffer *buf = NULL; + GstFlowReturn flow_ret = GST_FLOW_OK; + GstOMXAcquireBufferReturn acq_return; + OMX_ERRORTYPE err; + + acq_return = gst_omx_port_acquire_buffer (port, &buf); + if (acq_return == GST_OMX_ACQUIRE_BUFFER_ERROR) { + goto component_error; + } else if (acq_return == GST_OMX_ACQUIRE_BUFFER_FLUSHING) { + goto flushing; + } else if (acq_return == GST_OMX_ACQUIRE_BUFFER_EOS) { + goto eos; + } + + if (!gst_pad_has_current_caps (GST_AUDIO_DECODER_SRC_PAD (self)) || + acq_return == GST_OMX_ACQUIRE_BUFFER_RECONFIGURE) { + OMX_PARAM_PORTDEFINITIONTYPE port_def; + OMX_AUDIO_PARAM_PCMMODETYPE pcm_param; + GstAudioChannelPosition omx_position[OMX_AUDIO_MAXCHANNELS]; + gint i; + + GST_DEBUG_OBJECT (self, "Port settings have changed, updating caps"); + + /* Reallocate all buffers */ + if (acq_return == GST_OMX_ACQUIRE_BUFFER_RECONFIGURE + && gst_omx_port_is_enabled (port)) { + err = gst_omx_port_set_enabled (port, FALSE); + if (err != OMX_ErrorNone) + 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) + goto reconfigure_error; + + err = gst_omx_port_wait_enabled (port, 1 * GST_SECOND); + if (err != OMX_ErrorNone) + goto reconfigure_error; + } + + /* Just update caps */ + GST_AUDIO_DECODER_STREAM_LOCK (self); + + gst_omx_port_get_port_definition (port, &port_def); + g_assert (port_def.format.audio.eEncoding == OMX_AUDIO_CodingPCM); + + GST_OMX_INIT_STRUCT (&pcm_param); + pcm_param.nPortIndex = self->dec_in_port->index; + err = + gst_omx_component_get_parameter (self->dec, OMX_IndexParamAudioPcm, + &pcm_param); + if (err != OMX_ErrorNone) { + GST_ERROR_OBJECT (self, "Failed to get PCM parameters: %s (0x%08x)", + gst_omx_error_to_string (err), err); + goto caps_failed; + } + + g_assert (pcm_param.ePCMMode == OMX_AUDIO_PCMModeLinear); + g_assert (pcm_param.bInterleaved == OMX_TRUE); + + gst_audio_info_init (&self->info); + + for (i = 0; i < pcm_param.nChannels; i++) { + switch (pcm_param.eChannelMapping[i]) { + case OMX_AUDIO_ChannelLF: + omx_position[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; + break; + case OMX_AUDIO_ChannelRF: + omx_position[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; + break; + case OMX_AUDIO_ChannelCF: + omx_position[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER; + break; + case OMX_AUDIO_ChannelLS: + omx_position[i] = GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT; + break; + case OMX_AUDIO_ChannelRS: + omx_position[i] = GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT; + break; + case OMX_AUDIO_ChannelLFE: + omx_position[i] = GST_AUDIO_CHANNEL_POSITION_LFE1; + break; + case OMX_AUDIO_ChannelCS: + omx_position[i] = GST_AUDIO_CHANNEL_POSITION_REAR_CENTER; + break; + case OMX_AUDIO_ChannelLR: + omx_position[i] = GST_AUDIO_CHANNEL_POSITION_REAR_LEFT; + break; + case OMX_AUDIO_ChannelRR: + omx_position[i] = GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT; + break; + case OMX_AUDIO_ChannelNone: + default: + /* This will break the outer loop too as the + * i == pcm_param.nChannels afterwards */ + for (i = 0; i < pcm_param.nChannels; i++) + omx_position[i] = GST_AUDIO_CHANNEL_POSITION_NONE; + break; + } + } + if (pcm_param.nChannels == 1 + && omx_position[0] == GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER) + omx_position[0] = GST_AUDIO_CHANNEL_POSITION_MONO; + + memcpy (self->position, omx_position, sizeof (omx_position)); + gst_audio_channel_positions_to_valid_order (self->position, + pcm_param.nChannels); + self->needs_reorder = + (memcmp (self->position, omx_position, + sizeof (GstAudioChannelPosition) * pcm_param.nChannels) != 0); + if (self->needs_reorder) + gst_audio_get_channel_reorder_map (pcm_param.nChannels, self->position, + omx_position, self->reorder_map); + + gst_audio_info_set_format (&self->info, + gst_audio_format_build_integer (pcm_param.eNumData == + OMX_NumericalDataSigned, + pcm_param.eEndian == + OMX_EndianLittle ? G_LITTLE_ENDIAN : G_BIG_ENDIAN, + pcm_param.nBitPerSample, pcm_param.nBitPerSample), + pcm_param.nSamplingRate, pcm_param.nChannels, self->position); + + GST_DEBUG_OBJECT (self, + "Setting output state: format %s, rate %u, channels %u", + gst_audio_format_to_string (self->info.finfo->format), + (guint) pcm_param.nSamplingRate, (guint) pcm_param.nChannels); + + if (!gst_audio_decoder_set_output_format (GST_AUDIO_DECODER (self), + &self->info) + || !gst_audio_decoder_negotiate (GST_AUDIO_DECODER (self))) { + if (buf) + gst_omx_port_release_buffer (port, buf); + goto caps_failed; + } + + GST_AUDIO_DECODER_STREAM_UNLOCK (self); + + if (acq_return == GST_OMX_ACQUIRE_BUFFER_RECONFIGURE) { + 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_populate (port); + if (err != OMX_ErrorNone) + goto reconfigure_error; + + err = gst_omx_port_mark_reconfigured (port); + if (err != OMX_ErrorNone) + goto reconfigure_error; + } + + /* Now get a buffer */ + if (acq_return != GST_OMX_ACQUIRE_BUFFER_OK) { + return; + } + } + + g_assert (acq_return == GST_OMX_ACQUIRE_BUFFER_OK); + if (!buf) { + g_assert ((klass->cdata.hacks & GST_OMX_HACK_NO_EMPTY_EOS_BUFFER)); + GST_AUDIO_DECODER_STREAM_LOCK (self); + goto eos; + } + + /* This prevents a deadlock between the srcpad stream + * lock and the audiocodec stream lock, if ::reset() + * is called at the wrong time + */ + if (gst_omx_port_is_flushing (port)) { + GST_DEBUG_OBJECT (self, "Flushing"); + gst_omx_port_release_buffer (port, buf); + goto flushing; + } + + GST_DEBUG_OBJECT (self, "Handling buffer: 0x%08x %" G_GUINT64_FORMAT, + (guint) buf->omx_buf->nFlags, (guint64) buf->omx_buf->nTimeStamp); + + GST_AUDIO_DECODER_STREAM_LOCK (self); + + if (buf->omx_buf->nFilledLen > 0) { + GstBuffer *outbuf; + gint nframes, spf; + GstMapInfo minfo; + GstOMXAudioDecClass *klass = GST_OMX_AUDIO_DEC_GET_CLASS (self); + + GST_DEBUG_OBJECT (self, "Handling output data"); + + if (buf->omx_buf->nFilledLen % self->info.bpf != 0) { + gst_omx_port_release_buffer (port, buf); + goto invalid_buffer; + } + + outbuf = + gst_audio_decoder_allocate_output_buffer (GST_AUDIO_DECODER (self), + buf->omx_buf->nFilledLen); + + gst_buffer_map (outbuf, &minfo, GST_MAP_WRITE); + if (self->needs_reorder) { + gint i, n_samples, c, n_channels; + gint *reorder_map = self->reorder_map; + gint16 *dest, *source; + + dest = (gint16 *) minfo.data; + source = (gint16 *) (buf->omx_buf->pBuffer + buf->omx_buf->nOffset); + n_samples = buf->omx_buf->nFilledLen / self->info.bpf; + n_channels = self->info.channels; + + for (i = 0; i < n_samples; i++) { + for (c = 0; c < n_channels; c++) { + dest[i * n_channels + reorder_map[c]] = source[i * n_channels + c]; + } + } + } else { + memcpy (minfo.data, buf->omx_buf->pBuffer + buf->omx_buf->nOffset, + buf->omx_buf->nFilledLen); + } + gst_buffer_unmap (outbuf, &minfo); + + nframes = 1; + spf = klass->get_samples_per_frame (self, self->dec_out_port); + if (spf != -1) { + nframes = buf->omx_buf->nFilledLen / self->info.bpf; + if (nframes % spf != 0) + GST_WARNING_OBJECT (self, "Output buffer does not contain an integer " + "number of input frames (frames: %d, spf: %d)", nframes, spf); + nframes = (nframes + spf - 1) / spf; + } + + GST_BUFFER_TIMESTAMP (outbuf) = + gst_util_uint64_scale (buf->omx_buf->nTimeStamp, GST_SECOND, + OMX_TICKS_PER_SECOND); + if (buf->omx_buf->nTickCount != 0) + GST_BUFFER_DURATION (outbuf) = + gst_util_uint64_scale (buf->omx_buf->nTickCount, GST_SECOND, + OMX_TICKS_PER_SECOND); + + flow_ret = + gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (self), outbuf, + nframes); + } + + GST_DEBUG_OBJECT (self, "Read frame from component"); + + GST_DEBUG_OBJECT (self, "Finished frame: %s", gst_flow_get_name (flow_ret)); + + if (buf) { + err = gst_omx_port_release_buffer (port, buf); + if (err != OMX_ErrorNone) + goto release_error; + } + + self->downstream_flow_ret = flow_ret; + + if (flow_ret != GST_FLOW_OK) + goto flow_error; + + GST_AUDIO_DECODER_STREAM_UNLOCK (self); + + return; + +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->dec), + gst_omx_component_get_last_error (self->dec))); + gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); + gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); + self->downstream_flow_ret = GST_FLOW_ERROR; + self->started = FALSE; + return; + } + +flushing: + { + GST_DEBUG_OBJECT (self, "Flushing -- stopping task"); + gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); + self->downstream_flow_ret = GST_FLOW_FLUSHING; + self->started = FALSE; + return; + } + +eos: + { + g_mutex_lock (&self->drain_lock); + if (self->draining) { + GST_DEBUG_OBJECT (self, "Drained"); + self->draining = FALSE; + g_cond_broadcast (&self->drain_cond); + flow_ret = GST_FLOW_OK; + gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); + } else { + GST_DEBUG_OBJECT (self, "Component signalled EOS"); + flow_ret = GST_FLOW_EOS; + } + g_mutex_unlock (&self->drain_lock); + + GST_AUDIO_DECODER_STREAM_LOCK (self); + self->downstream_flow_ret = flow_ret; + + /* Here we fallback and pause the task for the EOS case */ + if (flow_ret != GST_FLOW_OK) + goto flow_error; + + GST_AUDIO_DECODER_STREAM_UNLOCK (self); + + return; + } + +flow_error: + { + if (flow_ret == GST_FLOW_EOS) { + GST_DEBUG_OBJECT (self, "EOS"); + + gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), + gst_event_new_eos ()); + gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); + self->started = FALSE; + } else if (flow_ret < GST_FLOW_EOS) { + GST_ELEMENT_ERROR (self, STREAM, FAILED, + ("Internal data stream error."), ("stream stopped, reason %s", + gst_flow_get_name (flow_ret))); + + gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), + gst_event_new_eos ()); + gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); + self->started = FALSE; + } else if (flow_ret == GST_FLOW_FLUSHING) { + GST_DEBUG_OBJECT (self, "Flushing -- stopping task"); + gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); + self->started = FALSE; + } + GST_AUDIO_DECODER_STREAM_UNLOCK (self); + return; + } + +reconfigure_error: + { + GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, (NULL), + ("Unable to reconfigure output port")); + gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); + gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); + self->downstream_flow_ret = GST_FLOW_ERROR; + self->started = FALSE; + return; + } + +invalid_buffer: + { + GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, (NULL), + ("Invalid sized input buffer")); + gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); + gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); + self->downstream_flow_ret = GST_FLOW_NOT_NEGOTIATED; + self->started = FALSE; + GST_AUDIO_DECODER_STREAM_UNLOCK (self); + return; + } + +caps_failed: + { + GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, (NULL), ("Failed to set caps")); + gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); + gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); + GST_AUDIO_DECODER_STREAM_UNLOCK (self); + self->downstream_flow_ret = GST_FLOW_NOT_NEGOTIATED; + self->started = FALSE; + return; + } +release_error: + { + GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, (NULL), + ("Failed to relase output buffer to component: %s (0x%08x)", + gst_omx_error_to_string (err), err)); + gst_pad_push_event (GST_AUDIO_DECODER_SRC_PAD (self), gst_event_new_eos ()); + gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self)); + self->downstream_flow_ret = GST_FLOW_ERROR; + self->started = FALSE; + GST_AUDIO_DECODER_STREAM_UNLOCK (self); + return; + } +} + +static gboolean +gst_omx_audio_dec_start (GstAudioDecoder * decoder) +{ + GstOMXAudioDec *self; + + self = GST_OMX_AUDIO_DEC (decoder); + + self->last_upstream_ts = 0; + self->eos = FALSE; + self->downstream_flow_ret = GST_FLOW_OK; + + return TRUE; +} + +static gboolean +gst_omx_audio_dec_stop (GstAudioDecoder * decoder) +{ + GstOMXAudioDec *self; + + self = GST_OMX_AUDIO_DEC (decoder); + + GST_DEBUG_OBJECT (self, "Stopping decoder"); + + gst_omx_port_set_flushing (self->dec_in_port, 5 * GST_SECOND, TRUE); + gst_omx_port_set_flushing (self->dec_out_port, 5 * GST_SECOND, TRUE); + + gst_pad_stop_task (GST_AUDIO_DECODER_SRC_PAD (decoder)); + + if (gst_omx_component_get_state (self->dec, 0) > OMX_StateIdle) + gst_omx_component_set_state (self->dec, OMX_StateIdle); + + self->downstream_flow_ret = GST_FLOW_FLUSHING; + self->started = FALSE; + self->eos = FALSE; + + g_mutex_lock (&self->drain_lock); + self->draining = FALSE; + g_cond_broadcast (&self->drain_cond); + g_mutex_unlock (&self->drain_lock); + + gst_omx_component_get_state (self->dec, 5 * GST_SECOND); + + gst_buffer_replace (&self->codec_data, NULL); + + GST_DEBUG_OBJECT (self, "Stopped decoder"); + + return TRUE; +} + +static gboolean +gst_omx_audio_dec_set_format (GstAudioDecoder * decoder, GstCaps * caps) +{ + GstOMXAudioDec *self; + GstOMXAudioDecClass *klass; + GstStructure *s; + const GValue *codec_data; + gboolean is_format_change = FALSE; + gboolean needs_disable = FALSE; + + self = GST_OMX_AUDIO_DEC (decoder); + klass = GST_OMX_AUDIO_DEC_GET_CLASS (decoder); + + GST_DEBUG_OBJECT (self, "Setting new caps %" GST_PTR_FORMAT, caps); + + /* Check if the caps change is a real format change or if only irrelevant + * parts of the caps have changed or nothing at all. + */ + if (klass->is_format_change) + is_format_change = klass->is_format_change (self, self->dec_in_port, caps); + + needs_disable = + gst_omx_component_get_state (self->dec, + GST_CLOCK_TIME_NONE) != OMX_StateLoaded; + /* If the component is not in Loaded state and a real format change happens + * we have to disable the port and re-allocate all buffers. If no real + * format change happened we can just exit here. + */ + if (needs_disable && !is_format_change) { + GST_DEBUG_OBJECT (self, + "Already running and caps did not change the format"); + return TRUE; + } + + if (needs_disable && is_format_change) { + GstOMXPort *out_port = self->dec_out_port; + + GST_DEBUG_OBJECT (self, "Need to disable and drain decoder"); + + gst_omx_audio_dec_drain (self, FALSE); + gst_omx_audio_dec_flush (decoder, FALSE); + gst_omx_port_set_flushing (out_port, 5 * GST_SECOND, TRUE); + + if (klass->cdata.hacks & GST_OMX_HACK_NO_COMPONENT_RECONFIGURE) { + GST_AUDIO_DECODER_STREAM_UNLOCK (self); + gst_omx_audio_dec_stop (GST_AUDIO_DECODER (self)); + gst_omx_audio_dec_close (GST_AUDIO_DECODER (self)); + GST_AUDIO_DECODER_STREAM_LOCK (self); + + if (!gst_omx_audio_dec_open (GST_AUDIO_DECODER (self))) + return FALSE; + needs_disable = FALSE; + } else { + if (gst_omx_port_set_enabled (self->dec_in_port, FALSE) != OMX_ErrorNone) + return FALSE; + if (gst_omx_port_set_enabled (out_port, FALSE) != OMX_ErrorNone) + return FALSE; + if (gst_omx_port_wait_buffers_released (self->dec_in_port, + 5 * GST_SECOND) != OMX_ErrorNone) + return FALSE; + if (gst_omx_port_wait_buffers_released (out_port, + 1 * GST_SECOND) != OMX_ErrorNone) + return FALSE; + if (gst_omx_port_deallocate_buffers (self->dec_in_port) != OMX_ErrorNone) + return FALSE; + if (gst_omx_port_deallocate_buffers (self->dec_out_port) != OMX_ErrorNone) + return FALSE; + if (gst_omx_port_wait_enabled (self->dec_in_port, + 1 * GST_SECOND) != OMX_ErrorNone) + return FALSE; + if (gst_omx_port_wait_enabled (out_port, 1 * GST_SECOND) != OMX_ErrorNone) + return FALSE; + } + + GST_DEBUG_OBJECT (self, "Decoder drained and disabled"); + } + + if (klass->set_format) { + if (!klass->set_format (self, self->dec_in_port, caps)) { + GST_ERROR_OBJECT (self, "Subclass failed to set the new format"); + return FALSE; + } + } + + GST_DEBUG_OBJECT (self, "Updating outport port definition"); + if (gst_omx_port_update_port_definition (self->dec_out_port, + NULL) != OMX_ErrorNone) + return FALSE; + + /* Get codec data from caps */ + gst_buffer_replace (&self->codec_data, NULL); + s = gst_caps_get_structure (caps, 0); + codec_data = gst_structure_get_value (s, "codec_data"); + if (codec_data) { + /* Vorbis and some other codecs have multiple buffers in + * the stream-header field */ + self->codec_data = gst_value_get_buffer (codec_data); + if (self->codec_data) + gst_buffer_ref (self->codec_data); + } + + GST_DEBUG_OBJECT (self, "Enabling component"); + + if (needs_disable) { + if (gst_omx_port_set_enabled (self->dec_in_port, TRUE) != OMX_ErrorNone) + return FALSE; + if (gst_omx_port_allocate_buffers (self->dec_in_port) != OMX_ErrorNone) + return FALSE; + if (gst_omx_port_wait_enabled (self->dec_in_port, + 5 * GST_SECOND) != OMX_ErrorNone) + return FALSE; + if (gst_omx_port_mark_reconfigured (self->dec_in_port) != OMX_ErrorNone) + return FALSE; + } else { + /* Disable output port */ + if (gst_omx_port_set_enabled (self->dec_out_port, FALSE) != OMX_ErrorNone) + return FALSE; + + if (gst_omx_port_wait_enabled (self->dec_out_port, + 1 * GST_SECOND) != OMX_ErrorNone) + return FALSE; + + if (gst_omx_component_set_state (self->dec, OMX_StateIdle) != OMX_ErrorNone) + return FALSE; + + /* Need to allocate buffers to reach Idle state */ + if (gst_omx_port_allocate_buffers (self->dec_in_port) != OMX_ErrorNone) + return FALSE; + + if (gst_omx_component_get_state (self->dec, + GST_CLOCK_TIME_NONE) != OMX_StateIdle) + return FALSE; + + if (gst_omx_component_set_state (self->dec, + OMX_StateExecuting) != OMX_ErrorNone) + return FALSE; + + if (gst_omx_component_get_state (self->dec, + GST_CLOCK_TIME_NONE) != OMX_StateExecuting) + return FALSE; + } + + /* Unset flushing to allow ports to accept data again */ + gst_omx_port_set_flushing (self->dec_in_port, 5 * GST_SECOND, FALSE); + gst_omx_port_set_flushing (self->dec_out_port, 5 * GST_SECOND, FALSE); + + if (gst_omx_component_get_last_error (self->dec) != OMX_ErrorNone) { + GST_ERROR_OBJECT (self, "Component in error state: %s (0x%08x)", + gst_omx_component_get_last_error_string (self->dec), + gst_omx_component_get_last_error (self->dec)); + return FALSE; + } + + /* Start the srcpad loop again */ + GST_DEBUG_OBJECT (self, "Starting task again"); + self->downstream_flow_ret = GST_FLOW_OK; + gst_pad_start_task (GST_AUDIO_DECODER_SRC_PAD (self), + (GstTaskFunction) gst_omx_audio_dec_loop, decoder, NULL); + + return TRUE; +} + +static void +gst_omx_audio_dec_flush (GstAudioDecoder * decoder, gboolean hard) +{ + GstOMXAudioDec *self = GST_OMX_AUDIO_DEC (decoder); + + GST_DEBUG_OBJECT (self, "Resetting decoder"); + + gst_omx_port_set_flushing (self->dec_in_port, 5 * GST_SECOND, TRUE); + gst_omx_port_set_flushing (self->dec_out_port, 5 * GST_SECOND, TRUE); + + /* Wait until the srcpad loop is finished */ + GST_AUDIO_ENCODER_STREAM_UNLOCK (self); + GST_PAD_STREAM_LOCK (GST_AUDIO_ENCODER_SRC_PAD (self)); + GST_PAD_STREAM_UNLOCK (GST_AUDIO_ENCODER_SRC_PAD (self)); + GST_AUDIO_ENCODER_STREAM_LOCK (self); + + gst_omx_port_set_flushing (self->dec_in_port, 5 * GST_SECOND, FALSE); + gst_omx_port_set_flushing (self->dec_out_port, 5 * GST_SECOND, FALSE); + gst_omx_port_populate (self->dec_out_port); + + /* Start the srcpad loop again */ + self->last_upstream_ts = 0; + self->downstream_flow_ret = GST_FLOW_OK; + self->eos = FALSE; + gst_pad_start_task (GST_AUDIO_DECODER_SRC_PAD (self), + (GstTaskFunction) gst_omx_audio_dec_loop, decoder, NULL); +} + +static GstFlowReturn +gst_omx_audio_dec_handle_frame (GstAudioDecoder * decoder, GstBuffer * inbuf) +{ + GstOMXAcquireBufferReturn acq_ret = GST_OMX_ACQUIRE_BUFFER_ERROR; + GstOMXAudioDec *self; + GstOMXPort *port; + GstOMXBuffer *buf; + GstBuffer *codec_data = NULL; + guint offset = 0; + GstClockTime timestamp, duration; + OMX_ERRORTYPE err; + GstMapInfo minfo; + + self = GST_OMX_AUDIO_DEC (decoder); + + GST_DEBUG_OBJECT (self, "Handling frame"); + + /* Make sure to keep a reference to the input here, + * it can be unreffed from the other thread if + * finish_frame() is called */ + if (inbuf) + gst_buffer_ref (inbuf); + + if (self->eos) { + GST_WARNING_OBJECT (self, "Got frame after EOS"); + if (inbuf) + gst_buffer_unref (inbuf); + return GST_FLOW_EOS; + } + + if (self->downstream_flow_ret != GST_FLOW_OK) { + if (inbuf) + gst_buffer_unref (inbuf); + return self->downstream_flow_ret; + } + + if (inbuf == NULL) + return gst_omx_audio_dec_drain (self, TRUE); + + timestamp = GST_BUFFER_TIMESTAMP (inbuf); + duration = GST_BUFFER_DURATION (inbuf); + + port = self->dec_in_port; + + gst_buffer_map (inbuf, &minfo, GST_MAP_READ); + + while (offset < minfo.size) { + /* Make sure to release the base class stream lock, otherwise + * _loop() can't call _finish_frame() and we might block forever + * because no input buffers are released */ + GST_AUDIO_DECODER_STREAM_UNLOCK (self); + acq_ret = gst_omx_port_acquire_buffer (port, &buf); + + if (acq_ret == GST_OMX_ACQUIRE_BUFFER_ERROR) { + GST_AUDIO_DECODER_STREAM_LOCK (self); + goto component_error; + } else if (acq_ret == GST_OMX_ACQUIRE_BUFFER_FLUSHING) { + GST_AUDIO_DECODER_STREAM_LOCK (self); + goto flushing; + } else if (acq_ret == GST_OMX_ACQUIRE_BUFFER_RECONFIGURE) { + /* Reallocate all buffers */ + err = gst_omx_port_set_enabled (port, FALSE); + if (err != OMX_ErrorNone) { + GST_AUDIO_DECODER_STREAM_LOCK (self); + goto reconfigure_error; + } + + err = gst_omx_port_wait_buffers_released (port, 5 * GST_SECOND); + if (err != OMX_ErrorNone) { + GST_AUDIO_DECODER_STREAM_LOCK (self); + goto reconfigure_error; + } + + err = gst_omx_port_deallocate_buffers (port); + if (err != OMX_ErrorNone) { + GST_AUDIO_DECODER_STREAM_LOCK (self); + goto reconfigure_error; + } + + err = gst_omx_port_wait_enabled (port, 1 * GST_SECOND); + if (err != OMX_ErrorNone) { + GST_AUDIO_DECODER_STREAM_LOCK (self); + goto reconfigure_error; + } + + err = gst_omx_port_set_enabled (port, TRUE); + if (err != OMX_ErrorNone) { + GST_AUDIO_DECODER_STREAM_LOCK (self); + goto reconfigure_error; + } + + err = gst_omx_port_allocate_buffers (port); + if (err != OMX_ErrorNone) { + GST_AUDIO_DECODER_STREAM_LOCK (self); + goto reconfigure_error; + } + + err = gst_omx_port_wait_enabled (port, 5 * GST_SECOND); + if (err != OMX_ErrorNone) { + GST_AUDIO_DECODER_STREAM_LOCK (self); + goto reconfigure_error; + } + + err = gst_omx_port_mark_reconfigured (port); + if (err != OMX_ErrorNone) { + GST_AUDIO_DECODER_STREAM_LOCK (self); + goto reconfigure_error; + } + + /* Now get a new buffer and fill it */ + GST_AUDIO_DECODER_STREAM_LOCK (self); + continue; + } + GST_AUDIO_DECODER_STREAM_LOCK (self); + + g_assert (acq_ret == GST_OMX_ACQUIRE_BUFFER_OK && buf != NULL); + + if (buf->omx_buf->nAllocLen - buf->omx_buf->nOffset <= 0) { + gst_omx_port_release_buffer (port, buf); + goto full_buffer; + } + + if (self->downstream_flow_ret != GST_FLOW_OK) { + gst_omx_port_release_buffer (port, buf); + goto flow_error; + } + + if (self->codec_data) { + GST_DEBUG_OBJECT (self, "Passing codec data to the component"); + + codec_data = self->codec_data; + + if (buf->omx_buf->nAllocLen - buf->omx_buf->nOffset < + gst_buffer_get_size (codec_data)) { + gst_omx_port_release_buffer (port, buf); + goto too_large_codec_data; + } + + buf->omx_buf->nFlags |= OMX_BUFFERFLAG_CODECCONFIG; + buf->omx_buf->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME; + buf->omx_buf->nFilledLen = gst_buffer_get_size (codec_data); + gst_buffer_extract (codec_data, 0, + buf->omx_buf->pBuffer + buf->omx_buf->nOffset, + buf->omx_buf->nFilledLen); + + if (GST_CLOCK_TIME_IS_VALID (timestamp)) + buf->omx_buf->nTimeStamp = + gst_util_uint64_scale (timestamp, OMX_TICKS_PER_SECOND, GST_SECOND); + else + buf->omx_buf->nTimeStamp = 0; + buf->omx_buf->nTickCount = 0; + + self->started = TRUE; + err = gst_omx_port_release_buffer (port, buf); + gst_buffer_replace (&self->codec_data, NULL); + if (err != OMX_ErrorNone) + goto release_error; + /* Acquire new buffer for the actual frame */ + continue; + } + + /* Now handle the frame */ + GST_DEBUG_OBJECT (self, "Passing frame offset %d to the component", offset); + + /* Copy the buffer content in chunks of size as requested + * by the port */ + buf->omx_buf->nFilledLen = + MIN (minfo.size - offset, + buf->omx_buf->nAllocLen - buf->omx_buf->nOffset); + gst_buffer_extract (inbuf, offset, + buf->omx_buf->pBuffer + buf->omx_buf->nOffset, + buf->omx_buf->nFilledLen); + + if (timestamp != GST_CLOCK_TIME_NONE) { + buf->omx_buf->nTimeStamp = + gst_util_uint64_scale (timestamp, OMX_TICKS_PER_SECOND, GST_SECOND); + self->last_upstream_ts = timestamp; + } else { + buf->omx_buf->nTimeStamp = 0; + } + + if (duration != GST_CLOCK_TIME_NONE && offset == 0) { + buf->omx_buf->nTickCount = + gst_util_uint64_scale (duration, OMX_TICKS_PER_SECOND, GST_SECOND); + self->last_upstream_ts += duration; + } else { + buf->omx_buf->nTickCount = 0; + } + + if (offset == 0) + buf->omx_buf->nFlags |= OMX_BUFFERFLAG_SYNCFRAME; + + /* TODO: Set flags + * - OMX_BUFFERFLAG_DECODEONLY for buffers that are outside + * the segment + */ + + offset += buf->omx_buf->nFilledLen; + + if (offset == minfo.size) + buf->omx_buf->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME; + + self->started = TRUE; + err = gst_omx_port_release_buffer (port, buf); + if (err != OMX_ErrorNone) + goto release_error; + } + + GST_DEBUG_OBJECT (self, "Passed frame to component"); + if (inbuf) + gst_buffer_unref (inbuf); + + return self->downstream_flow_ret; + +full_buffer: + { + if (inbuf) + gst_buffer_unref (inbuf); + + GST_ELEMENT_ERROR (self, LIBRARY, FAILED, (NULL), + ("Got OpenMAX buffer with no free space (%p, %u/%u)", buf, + (guint) buf->omx_buf->nOffset, (guint) buf->omx_buf->nAllocLen)); + return GST_FLOW_ERROR; + } + +flow_error: + { + if (inbuf) + gst_buffer_unref (inbuf); + + return self->downstream_flow_ret; + } + +too_large_codec_data: + { + if (inbuf) + gst_buffer_unref (inbuf); + + GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL), + ("codec_data larger than supported by OpenMAX port " + "(%" G_GSIZE_FORMAT " > %u)", gst_buffer_get_size (codec_data), + (guint) self->dec_in_port->port_def.nBufferSize)); + return GST_FLOW_ERROR; + } + +component_error: + { + if (inbuf) + gst_buffer_unref (inbuf); + + GST_ELEMENT_ERROR (self, LIBRARY, FAILED, (NULL), + ("OpenMAX component in error state %s (0x%08x)", + gst_omx_component_get_last_error_string (self->dec), + gst_omx_component_get_last_error (self->dec))); + return GST_FLOW_ERROR; + } + +flushing: + { + if (inbuf) + gst_buffer_unref (inbuf); + + GST_DEBUG_OBJECT (self, "Flushing -- returning FLUSHING"); + return GST_FLOW_FLUSHING; + } +reconfigure_error: + { + if (inbuf) + gst_buffer_unref (inbuf); + + GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, (NULL), + ("Unable to reconfigure input port")); + return GST_FLOW_ERROR; + } +release_error: + { + if (inbuf) + gst_buffer_unref (inbuf); + + 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 GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_omx_audio_dec_drain (GstOMXAudioDec * self, gboolean is_eos) +{ + GstOMXAudioDecClass *klass; + GstOMXBuffer *buf; + GstOMXAcquireBufferReturn acq_ret; + OMX_ERRORTYPE err; + + GST_DEBUG_OBJECT (self, "Draining component"); + + klass = GST_OMX_AUDIO_DEC_GET_CLASS (self); + + if (!self->started) { + GST_DEBUG_OBJECT (self, "Component not started yet"); + return GST_FLOW_OK; + } + self->started = FALSE; + + /* Don't send EOS buffer twice, this doesn't work */ + if (self->eos) { + GST_DEBUG_OBJECT (self, "Component is EOS already"); + return GST_FLOW_OK; + } + if (is_eos) + self->eos = TRUE; + + if ((klass->cdata.hacks & GST_OMX_HACK_NO_EMPTY_EOS_BUFFER)) { + GST_WARNING_OBJECT (self, "Component does not support empty EOS buffers"); + return GST_FLOW_OK; + } + + /* Make sure to release the base class stream lock, otherwise + * _loop() can't call _finish_frame() and we might block forever + * because no input buffers are released */ + GST_AUDIO_DECODER_STREAM_UNLOCK (self); + + /* Send an EOS buffer to the component and let the base + * class drop the EOS event. We will send it later when + * the EOS buffer arrives on the output port. */ + acq_ret = gst_omx_port_acquire_buffer (self->dec_in_port, &buf); + if (acq_ret != GST_OMX_ACQUIRE_BUFFER_OK) { + GST_AUDIO_DECODER_STREAM_LOCK (self); + GST_ERROR_OBJECT (self, "Failed to acquire buffer for draining: %d", + acq_ret); + return GST_FLOW_ERROR; + } + + g_mutex_lock (&self->drain_lock); + self->draining = TRUE; + buf->omx_buf->nFilledLen = 0; + buf->omx_buf->nTimeStamp = + gst_util_uint64_scale (self->last_upstream_ts, OMX_TICKS_PER_SECOND, + GST_SECOND); + buf->omx_buf->nTickCount = 0; + buf->omx_buf->nFlags |= OMX_BUFFERFLAG_EOS; + err = gst_omx_port_release_buffer (self->dec_in_port, buf); + if (err != OMX_ErrorNone) { + GST_ERROR_OBJECT (self, "Failed to drain component: %s (0x%08x)", + gst_omx_error_to_string (err), err); + g_mutex_unlock (&self->drain_lock); + GST_AUDIO_DECODER_STREAM_LOCK (self); + return GST_FLOW_ERROR; + } + + GST_DEBUG_OBJECT (self, "Waiting until component is drained"); + + if (G_UNLIKELY (self->dec->hacks & GST_OMX_HACK_DRAIN_MAY_NOT_RETURN)) { + gint64 wait_until = g_get_monotonic_time () + G_TIME_SPAN_SECOND / 2; + + if (!g_cond_wait_until (&self->drain_cond, &self->drain_lock, wait_until)) + GST_WARNING_OBJECT (self, "Drain timed out"); + else + GST_DEBUG_OBJECT (self, "Drained component"); + + } else { + g_cond_wait (&self->drain_cond, &self->drain_lock); + GST_DEBUG_OBJECT (self, "Drained component"); + } + + g_mutex_unlock (&self->drain_lock); + GST_AUDIO_DECODER_STREAM_LOCK (self); + + self->started = FALSE; + + return GST_FLOW_OK; +} diff --git a/omx/gstomxaudiodec.h b/omx/gstomxaudiodec.h new file mode 100644 index 0000000..e7d5a26 --- /dev/null +++ b/omx/gstomxaudiodec.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2014, Sebastian Dröge <sebastian@centricular.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 + * + */ + +#ifndef __GST_OMX_AUDIO_DEC_H__ +#define __GST_OMX_AUDIO_DEC_H__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> +#include <gst/audio/audio.h> +#include <gst/audio/gstaudiodecoder.h> + +#include "gstomx.h" + +G_BEGIN_DECLS + +#define GST_TYPE_OMX_AUDIO_DEC \ + (gst_omx_audio_dec_get_type()) +#define GST_OMX_AUDIO_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OMX_AUDIO_DEC,GstOMXAudioDec)) +#define GST_OMX_AUDIO_DEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OMX_AUDIO_DEC,GstOMXAudioDecClass)) +#define GST_OMX_AUDIO_DEC_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_OMX_AUDIO_DEC,GstOMXAudioDecClass)) +#define GST_IS_OMX_AUDIO_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OMX_AUDIO_DEC)) +#define GST_IS_OMX_AUDIO_DEC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OMX_AUDIO_DEC)) + +typedef struct _GstOMXAudioDec GstOMXAudioDec; +typedef struct _GstOMXAudioDecClass GstOMXAudioDecClass; + +struct _GstOMXAudioDec +{ + GstAudioDecoder parent; + + /* < protected > */ + GstOMXComponent *dec; + GstOMXPort *dec_in_port, *dec_out_port; + + GstBufferPool *in_port_pool, *out_port_pool; + + /* < private > */ + GstAudioInfo info; + GstAudioChannelPosition position[OMX_AUDIO_MAXCHANNELS]; + gint reorder_map[OMX_AUDIO_MAXCHANNELS]; + gboolean needs_reorder; + GstBuffer *codec_data; + /* TRUE if the component is configured and saw + * the first buffer */ + gboolean started; + + GstClockTime last_upstream_ts; + + /* Draining state */ + GMutex drain_lock; + GCond drain_cond; + /* TRUE if EOS buffers shouldn't be forwarded */ + gboolean draining; + + /* TRUE if upstream is EOS */ + gboolean eos; + + GstFlowReturn downstream_flow_ret; +}; + +struct _GstOMXAudioDecClass +{ + GstAudioDecoderClass parent_class; + + GstOMXClassData cdata; + + gboolean (*is_format_change) (GstOMXAudioDec * self, GstOMXPort * port, GstCaps * caps); + gboolean (*set_format) (GstOMXAudioDec * self, GstOMXPort * port, GstCaps * caps); + gint (*get_samples_per_frame) (GstOMXAudioDec * self, GstOMXPort * port); +}; + +GType gst_omx_audio_dec_get_type (void); + +G_END_DECLS + +#endif /* __GST_OMX_AUDIO_DEC_H__ */ diff --git a/omx/gstomxmp3dec.c b/omx/gstomxmp3dec.c new file mode 100644 index 0000000..f52a8f5 --- /dev/null +++ b/omx/gstomxmp3dec.c @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2014, Sebastian Dröge <sebastian@centricular.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 "gstomxmp3dec.h" + +GST_DEBUG_CATEGORY_STATIC (gst_omx_mp3_dec_debug_category); +#define GST_CAT_DEFAULT gst_omx_mp3_dec_debug_category + +/* prototypes */ +static gboolean gst_omx_mp3_dec_set_format (GstOMXAudioDec * dec, + GstOMXPort * port, GstCaps * caps); +static gboolean gst_omx_mp3_dec_is_format_change (GstOMXAudioDec * dec, + GstOMXPort * port, GstCaps * caps); +static gint gst_omx_mp3_dec_get_samples_per_frame (GstOMXAudioDec * dec, + GstOMXPort * port); + +/* class initialization */ + +#define DEBUG_INIT \ + GST_DEBUG_CATEGORY_INIT (gst_omx_mp3_dec_debug_category, "omxmp3dec", 0, \ + "debug category for gst-omx audio decoder base class"); + +G_DEFINE_TYPE_WITH_CODE (GstOMXMP3Dec, gst_omx_mp3_dec, + GST_TYPE_OMX_AUDIO_DEC, DEBUG_INIT); + + +static void +gst_omx_mp3_dec_class_init (GstOMXMP3DecClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstOMXAudioDecClass *audiodec_class = GST_OMX_AUDIO_DEC_CLASS (klass); + + audiodec_class->set_format = GST_DEBUG_FUNCPTR (gst_omx_mp3_dec_set_format); + audiodec_class->is_format_change = + GST_DEBUG_FUNCPTR (gst_omx_mp3_dec_is_format_change); + audiodec_class->get_samples_per_frame = + GST_DEBUG_FUNCPTR (gst_omx_mp3_dec_get_samples_per_frame); + + audiodec_class->cdata.default_sink_template_caps = "audio/mpeg, " + "mpegversion=(int)1, " + "layer=(int)3, " + "mpegaudioversion=(int)[1,3], " + "rate=(int)[8000,48000], " + "channels=(int)[1,2], " "parsed=(boolean) true"; + + gst_element_class_set_static_metadata (element_class, + "OpenMAX MP3 Audio Decoder", + "Codec/Decoder/Audio", + "Decode MP3 audio streams", + "Sebastian Dröge <sebastian@centricular.com>"); + + gst_omx_set_default_role (&audiodec_class->cdata, "audio_decoder.mp3"); +} + +static void +gst_omx_mp3_dec_init (GstOMXMP3Dec * self) +{ + self->spf = -1; +} + +static gboolean +gst_omx_mp3_dec_set_format (GstOMXAudioDec * dec, GstOMXPort * port, + GstCaps * caps) +{ + GstOMXMP3Dec *self = GST_OMX_MP3_DEC (dec); + OMX_PARAM_PORTDEFINITIONTYPE port_def; + OMX_AUDIO_PARAM_MP3TYPE mp3_param; + OMX_ERRORTYPE err; + GstStructure *s; + gint rate, channels, layer, mpegaudioversion; + + gst_omx_port_get_port_definition (port, &port_def); + port_def.format.audio.eEncoding = OMX_AUDIO_CodingMP3; + err = gst_omx_port_update_port_definition (port, &port_def); + if (err != OMX_ErrorNone) { + GST_ERROR_OBJECT (self, + "Failed to set MP3 format on component: %s (0x%08x)", + gst_omx_error_to_string (err), err); + return FALSE; + } + + GST_OMX_INIT_STRUCT (&mp3_param); + mp3_param.nPortIndex = port->index; + + err = + gst_omx_component_get_parameter (dec->dec, OMX_IndexParamAudioMp3, + &mp3_param); + if (err != OMX_ErrorNone) { + GST_ERROR_OBJECT (self, + "Failed to get MP3 parameters from component: %s (0x%08x)", + gst_omx_error_to_string (err), err); + return FALSE; + } + + s = gst_caps_get_structure (caps, 0); + + if (!gst_structure_get_int (s, "mpegaudioversion", &mpegaudioversion) || + !gst_structure_get_int (s, "layer", &layer) || + !gst_structure_get_int (s, "rate", &rate) || + !gst_structure_get_int (s, "channels", &channels)) { + GST_ERROR_OBJECT (self, "Incomplete caps"); + return FALSE; + } + + self->spf = (mpegaudioversion == 1 ? 1152 : 576); + + mp3_param.nChannels = channels; + mp3_param.nBitRate = 0; /* unknown */ + mp3_param.nSampleRate = rate; + mp3_param.nAudioBandWidth = 0; /* decoder decision */ + mp3_param.eChannelMode = 0; /* FIXME */ + if (mpegaudioversion == 1) + mp3_param.eFormat = OMX_AUDIO_MP3StreamFormatMP1Layer3; + else if (mpegaudioversion == 2) + mp3_param.eFormat = OMX_AUDIO_MP3StreamFormatMP2Layer3; + else + mp3_param.eFormat = OMX_AUDIO_MP3StreamFormatMP2_5Layer3; + + err = + gst_omx_component_set_parameter (dec->dec, OMX_IndexParamAudioMp3, + &mp3_param); + if (err != OMX_ErrorNone) { + GST_ERROR_OBJECT (self, "Error setting MP3 parameters: %s (0x%08x)", + gst_omx_error_to_string (err), err); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_omx_mp3_dec_is_format_change (GstOMXAudioDec * dec, GstOMXPort * port, + GstCaps * caps) +{ + GstOMXMP3Dec *self = GST_OMX_MP3_DEC (dec); + OMX_AUDIO_PARAM_MP3TYPE mp3_param; + OMX_ERRORTYPE err; + GstStructure *s; + gint rate, channels, layer, mpegaudioversion; + + GST_OMX_INIT_STRUCT (&mp3_param); + mp3_param.nPortIndex = port->index; + + err = + gst_omx_component_get_parameter (dec->dec, OMX_IndexParamAudioMp3, + &mp3_param); + if (err != OMX_ErrorNone) { + GST_ERROR_OBJECT (self, + "Failed to get MP3 parameters from component: %s (0x%08x)", + gst_omx_error_to_string (err), err); + return FALSE; + } + + s = gst_caps_get_structure (caps, 0); + + if (!gst_structure_get_int (s, "mpegaudioversion", &mpegaudioversion) || + !gst_structure_get_int (s, "layer", &layer) || + !gst_structure_get_int (s, "rate", &rate) || + !gst_structure_get_int (s, "channels", &channels)) { + GST_ERROR_OBJECT (self, "Incomplete caps"); + return FALSE; + } + + if (mp3_param.nChannels != channels) + return TRUE; + + if (mp3_param.nSampleRate != rate) + return TRUE; + + if (mpegaudioversion == 1 + && mp3_param.eFormat != OMX_AUDIO_MP3StreamFormatMP1Layer3) + return TRUE; + if (mpegaudioversion == 2 + && mp3_param.eFormat != OMX_AUDIO_MP3StreamFormatMP2Layer3) + return TRUE; + if (mpegaudioversion == 3 + && mp3_param.eFormat != OMX_AUDIO_MP3StreamFormatMP2_5Layer3) + return TRUE; + + return FALSE; +} + +static gint +gst_omx_mp3_dec_get_samples_per_frame (GstOMXAudioDec * dec, GstOMXPort * port) +{ + return GST_OMX_MP3_DEC (dec)->spf; +} diff --git a/omx/gstomxmp3dec.h b/omx/gstomxmp3dec.h new file mode 100644 index 0000000..4f07659 --- /dev/null +++ b/omx/gstomxmp3dec.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014, Sebastian Dröge <sebastian@centricular.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 + * + */ + +#ifndef __GST_OMX_MP3_DEC_H__ +#define __GST_OMX_MP3_DEC_H__ + +#include <gst/gst.h> +#include "gstomxaudiodec.h" + +G_BEGIN_DECLS + +#define GST_TYPE_OMX_MP3_DEC \ + (gst_omx_mp3_dec_get_type()) +#define GST_OMX_MP3_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OMX_MP3_DEC,GstOMXMP3Dec)) +#define GST_OMX_MP3_DEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OMX_MP3_DEC,GstOMXMP3DecClass)) +#define GST_OMX_MP3_DEC_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_OMX_MP3_DEC,GstOMXMP3DecClass)) +#define GST_IS_OMX_MP3_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OMX_MP3_DEC)) +#define GST_IS_OMX_MP3_DEC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OMX_MP3_DEC)) + +typedef struct _GstOMXMP3Dec GstOMXMP3Dec; +typedef struct _GstOMXMP3DecClass GstOMXMP3DecClass; + +struct _GstOMXMP3Dec +{ + GstOMXAudioDec parent; + gint spf; +}; + +struct _GstOMXMP3DecClass +{ + GstOMXAudioDecClass parent_class; +}; + +GType gst_omx_mp3_dec_get_type (void); + +G_END_DECLS + +#endif /* __GST_OMX_MP3_DEC_H__ */ + |