/*
 * 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 <stdio.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);

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), FALSE);
    gst_audio_decoder_set_drainable (GST_AUDIO_DECODER (self), TRUE);
    gst_audio_decoder_set_use_default_pad_acceptcaps (GST_AUDIO_DECODER_CAST
            (self), TRUE);
    GST_PAD_SET_ACCEPT_TEMPLATE (GST_AUDIO_DECODER_SINK_PAD (self));

    g_mutex_init (&self->drain_lock);
    g_cond_init (&self->drain_cond);

    self->output_adapter = gst_adapter_new ();
}

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 (&param);

        err =
            gst_omx_component_get_parameter (self->dec, 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 */
            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");

    gst_omx_component_set_state (self->dec, OMX_StateIdle);

    gst_omx_port_allocate_buffers(self->dec_in_port);
    gst_omx_port_allocate_buffers(self->dec_out_port);

    GST_DEBUG_OBJECT (self, "[YF][audio_dec]Opened decoder, state = OMX_StateIdle");

    if (klass->set_parament)
    {
        g_print("[YF][AUD-DEC]set_parament\n");
        klass->set_parament(self);
    }

    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);

    if (self->output_adapter)
        gst_object_unref (self->output_adapter);
    self->output_adapter = NULL;

    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;
    }

    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;
}

void dump_buf(GstBuffer* output_buf)
{
    GstMapInfo output_minfo;

    gst_buffer_map (output_buf, &output_minfo, GST_MAP_WRITE);

    FILE *ffp = NULL;
    ffp = fopen("/tmp/dec_done4.pcm", "a+");
    fwrite(output_minfo.data, output_minfo.size, 1, ffp);
    fclose(ffp);

    gst_buffer_unmap (output_buf, &output_minfo);
}

static void
gst_omx_audio_dec_loop (GstOMXAudioDec * self)
{
    GstOMXAudioDecClass *klass = GST_OMX_AUDIO_DEC_GET_CLASS (self);
    GstOMXPort *port = self->dec_out_port;
    GstOMXBuffer *buf = NULL;
    GstFlowReturn flow_ret = GST_FLOW_OK;
    GstOMXAcquireBufferReturn acq_return;
    OMX_ERRORTYPE err;
    gint spf;
    static gint err_count = 0;

    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];
        GstOMXAudioDecClass *klass = GST_OMX_AUDIO_DEC_GET_CLASS (self);
        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_out_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:
                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;
    	}

        if (omx_position[0] == GST_AUDIO_CHANNEL_POSITION_NONE && klass->get_channel_positions)
        {
            GST_WARNING_OBJECT (self, "Failed to get a valid channel layout, trying fallback");
            klass->get_channel_positions (self, self->dec_out_port, omx_position);
        }

        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) GST_OMX_GET_TICKS (buf->omx_buf->nTimeStamp));

    GST_AUDIO_DECODER_STREAM_LOCK (self);

    spf = klass->get_samples_per_frame (self, self->dec_out_port);

    if (buf->omx_buf->nFilledLen > 0)
    {
        GstBuffer *outbuf;
        GstMapInfo minfo;

        GST_DEBUG_OBJECT (self, "Handling output data");

        if (buf->omx_buf->nFilledLen % self->info.bpf != 0)
        {
            if (err_count < 3)
            {
                g_print ("[gstomxaudiodec] dismatch_err <%s %d>, size: %d, bpf: %d, err_cnt: %d\n ",
                    __func__, __LINE__, buf->omx_buf->nFilledLen, self->info.bpf, err_count);
                err_count++;
                goto dismatch_err;
            }
            else
            {
                g_print ("[gstomxaudiodec] invalid_buffer <%s %d>, size: %d, bpf: %d, err_cnt: %d\n ",
                    __func__, __LINE__, buf->omx_buf->nFilledLen, self->info.bpf, err_count);
                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 (0/*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);
        }
		
		if (!access("/tmp/dec_done3.conf", 0))
		{
			FILE *mfp3 = NULL;
			mfp3 = fopen("/tmp/dec_done3.pcm", "a+");
			fwrite(buf->omx_buf->pBuffer + buf->omx_buf->nOffset, buf->omx_buf->nFilledLen, 1, mfp3);
			fclose(mfp3);
		}

        gst_buffer_unmap (outbuf, &minfo);

        if (spf != -1)
        {
            gst_adapter_push (self->output_adapter, outbuf);
        }
        else
        {
            flow_ret = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (self), outbuf, 1);
        }
    }
    else
    {
        flow_ret = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (self), NULL, 1);
    }

    GST_DEBUG_OBJECT (self, "Read frame from component");

    if ((spf != -1) && (buf->omx_buf->nFilledLen > 0))
    {
        GstBuffer *outbuf;
        guint avail = gst_adapter_available (self->output_adapter);
        guint nframes;

        /* We take a multiple of codec frames and push them downstream */
        avail /= self->info.bpf;
        nframes = avail / spf;
        avail = nframes * spf;
        avail *= self->info.bpf;

        if (avail > 0)
        {
            outbuf = gst_adapter_take_buffer (self->output_adapter, avail);			
			if (!access("/tmp/dec_done4.conf", 0))
			{
				dump_buf(outbuf);
			}
            flow_ret = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (self), outbuf, nframes);
        }
    }

    GST_DEBUG_OBJECT (self, "Finished frame: %s", gst_flow_get_name (flow_ret));

dismatch_err:
    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");
        g_mutex_lock (&self->drain_lock);
        if (self->draining)
        {
            self->draining = FALSE;
            g_cond_broadcast (&self->drain_cond);
        }
        gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self));
        self->downstream_flow_ret = GST_FLOW_FLUSHING;
        self->started = FALSE;
        g_mutex_unlock (&self->drain_lock);
        return;
    }

eos:
    {
        spf = klass->get_samples_per_frame (self, self->dec_out_port);
        if (spf != -1)
        {
            GstBuffer *outbuf;
            guint avail = gst_adapter_available (self->output_adapter);
            guint nframes;

            /* On EOS we take the complete adapter content, no matter
             * if it is a multiple of the codec frame size or not.
             */
            avail /= self->info.bpf;
            nframes = (avail + spf - 1) / spf;
            avail *= self->info.bpf;

            if (avail > 0)
            {
                outbuf = gst_adapter_take_buffer (self->output_adapter, avail);
                flow_ret =
                    gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (self), outbuf,
                                                    nframes);
            }
        }

        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");
            g_mutex_lock (&self->drain_lock);
            if (self->draining)
            {
                self->draining = FALSE;
                g_cond_broadcast (&self->drain_cond);
            }
            gst_pad_pause_task (GST_AUDIO_DECODER_SRC_PAD (self));
            self->started = FALSE;
            g_mutex_unlock (&self->drain_lock);
        }
        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->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;

    g_mutex_lock (&self->drain_lock);
    self->draining = FALSE;
    g_cond_broadcast (&self->drain_cond);
    g_mutex_unlock (&self->drain_lock);

    gst_adapter_flush (self->output_adapter,
                       gst_adapter_available (self->output_adapter));

    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);
        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 ((klass->cdata.hacks & GST_OMX_HACK_NO_DISABLE_OUTPORT))
        {
            if (gst_omx_port_set_enabled (self->dec_out_port, TRUE) != OMX_ErrorNone)
                return FALSE;
            if (gst_omx_port_allocate_buffers (self->dec_out_port) != OMX_ErrorNone)
                return FALSE;

            if (gst_omx_port_wait_enabled (self->dec_out_port,
                                           5 * GST_SECOND) != 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
    {
        if (!(klass->cdata.hacks & GST_OMX_HACK_NO_DISABLE_OUTPORT))
        {
            /* 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;
        }
        else
        {
            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_port_allocate_buffers (self->dec_out_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;
    }

    self->downstream_flow_ret = GST_FLOW_OK;

    return TRUE;
}

static void
gst_omx_audio_dec_flush (GstAudioDecoder * decoder, gboolean hard)
{
    GstOMXAudioDec *self = GST_OMX_AUDIO_DEC (decoder);
    OMX_ERRORTYPE err = OMX_ErrorNone;

    GST_DEBUG_OBJECT (self, "Flushing decoder");

    if (gst_omx_component_get_state (self->dec, 0) == OMX_StateLoaded)
        return;

    /* 0) Pause the components */
    if (gst_omx_component_get_state (self->dec, 0) == OMX_StateExecuting)
    {
        gst_omx_component_set_state (self->dec, OMX_StatePause);
        gst_omx_component_get_state (self->dec, GST_CLOCK_TIME_NONE);
    }

    /* 1) Flush the ports */
    GST_DEBUG_OBJECT (self, "flushing ports");
    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);

    /* 2) Wait until the srcpad loop is stopped,
     * unlock GST_AUDIO_DECODER_STREAM_LOCK to prevent deadlocks
     * caused by using this lock from inside the loop function */
    GST_AUDIO_DECODER_STREAM_UNLOCK (self);
    gst_pad_stop_task (GST_AUDIO_DECODER_SRC_PAD (decoder));
    GST_DEBUG_OBJECT (self, "Flushing -- task stopped");
    GST_AUDIO_DECODER_STREAM_LOCK (self);

    /* 3) Resume components */
    gst_omx_component_set_state (self->dec, OMX_StateExecuting);
    gst_omx_component_get_state (self->dec, GST_CLOCK_TIME_NONE);

    /* 4) 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);

    err = gst_omx_port_populate (self->dec_out_port);

    if (err != OMX_ErrorNone)
    {
        GST_WARNING_OBJECT (self, "Failed to populate output port: %s (0x%08x)",
                            gst_omx_error_to_string (err), err);
    }

    /* Reset our state */
    gst_adapter_flush (self->output_adapter,
                       gst_adapter_available (self->output_adapter));
    self->last_upstream_ts = 0;
    self->downstream_flow_ret = GST_FLOW_OK;
    self->started = FALSE;
    GST_DEBUG_OBJECT (self, "Flush finished");
}

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");

    if ( gst_omx_component_get_state(self->dec, GST_CLOCK_TIME_NONE) == OMX_StateIdle )
    {
        gst_omx_component_set_state (self->dec, OMX_StateExecuting);
        GST_DEBUG_OBJECT (self, "[YF][audio_dec]Setting state to OMX_StateExecuting");
    }

    if (self->downstream_flow_ret != GST_FLOW_OK)
    {
        return self->downstream_flow_ret;
    }

    if (!self->started)
    {
        GST_DEBUG_OBJECT (self, "Starting task");
        gst_pad_start_task (GST_AUDIO_DECODER_SRC_PAD (self),
                            (GstTaskFunction) gst_omx_audio_dec_loop, decoder, NULL);
    }

    if (inbuf == NULL)
        return gst_omx_audio_dec_drain (self);

    /* Make sure to keep a reference to the input here,
     * it can be unreffed from the other thread if
     * finish_frame() is called */
    gst_buffer_ref (inbuf);

    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))
                GST_OMX_SET_TICKS (buf->omx_buf->nTimeStamp,
                                   gst_util_uint64_scale (timestamp, OMX_TICKS_PER_SECOND,
                                                          GST_SECOND));
            else
                GST_OMX_SET_TICKS (buf->omx_buf->nTimeStamp, G_GUINT64_CONSTANT (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)
        {
            GST_OMX_SET_TICKS (buf->omx_buf->nTimeStamp,
                               gst_util_uint64_scale (timestamp, OMX_TICKS_PER_SECOND, GST_SECOND));
            self->last_upstream_ts = timestamp;
        }
        else
        {
            GST_OMX_SET_TICKS (buf->omx_buf->nTimeStamp, G_GUINT64_CONSTANT (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_buffer_unmap (inbuf, &minfo);
    gst_buffer_unref (inbuf);

    GST_DEBUG_OBJECT (self, "Passed frame to component");

    return self->downstream_flow_ret;

full_buffer:
    {
        gst_buffer_unmap (inbuf, &minfo);
        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:
    {
        gst_buffer_unmap (inbuf, &minfo);
        gst_buffer_unref (inbuf);

        return self->downstream_flow_ret;
    }

too_large_codec_data:
    {
        gst_buffer_unmap (inbuf, &minfo);
        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:
    {
        gst_buffer_unmap (inbuf, &minfo);
        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:
    {
        gst_buffer_unmap (inbuf, &minfo);
        gst_buffer_unref (inbuf);

        GST_DEBUG_OBJECT (self, "Flushing -- returning FLUSHING");
        return GST_FLOW_FLUSHING;
    }
reconfigure_error:
    {
        gst_buffer_unmap (inbuf, &minfo);
        gst_buffer_unref (inbuf);

        GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, (NULL),
                           ("Unable to reconfigure input port"));
        return GST_FLOW_ERROR;
    }
release_error:
    {
        gst_buffer_unmap (inbuf, &minfo);
        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)
{
    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;

    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;
    GST_OMX_SET_TICKS (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);

    gst_adapter_flush (self->output_adapter,
                       gst_adapter_available (self->output_adapter));
    self->started = FALSE;

    return GST_FLOW_OK;
}
