/*****************************************************************************/
/* Copyright (c) 2009 NXP B.V.                                               */
/*                                                                           */
/* 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, using               */
/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA   */
/*                                                                           */
/*****************************************************************************/
/*

Rev Date        Author      Comments
--------------------------------------------------------------------------------
1   20090205    laird       First Version
2   20090313    laird       using writev to make it perform better.
3   20090317    laird       We need a preroll function otherwise we repeat first payload.
4   20090319    laird       Make sure we resend DSI after a seek operation.
5   20090323    laird       Adding support for lpcm/dts playback and adding mute.
6   20090326    laird       Fixing some more issues.
7   20090327    laird       Allow dumping of BurstDTS data - useful for NB debugging.
8   20090330    laird       Check against null not < 0.
9   20090406    laird       boolean not string (Doh!)
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/
/**
 * SECTION:stb22xframedaudio
 *
 * The stb22xframedaudio element takes in ES data ideally framed.
 * It then adds the proprietary meta data - See Design In Guide for details
 * This allows the audio decoders to play most input media formats.
 *
 * In some cases some extra data is passed to the decoders to make sure
 * they can decode (i.e MPEG4 AAC(ADIF)).
 */
/**************************************************************
* INCLUDE FILES                                               *
***************************************************************/
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

#ifdef HAVE_CONFIG_H
    #include <config.h>
#endif
#include <gst/gst.h>
#include <gst/gstmarshal.h>
#include <gst/video/video.h>
#include "gststb22xframedaudio.h"
#include "gststb22xframedaudio_utils.h"

/**************************************************************
* LOCAL MACROS                                                *
***************************************************************/

/**
 * Default values for plugin options.
 */
#define DEFAULT_SYNC FALSE
#define DEFAULT_DUMP FALSE
#define DEFAULT_MUTE FALSE
#define DEFAULT_STATE_ERROR STB22X_FRAMED_AUDIO_STATE_ERROR_NONE
#define GST_TYPE_STB22X_FRAMED_AUDIO_STATE_ERROR (gst_stb22x_framed_audio_state_error_get_type())

/**************************************************************
* LOCAL TYPEDEFS                                              *
***************************************************************/

/**
 * Enum of available properties on this component.
 */
enum
{
    PROP_0,
    PROP_STATE_ERROR,
    PROP_DUMP,
    PROP_DUMP_ES_FILENAME,
    PROP_SBMSINKNAME,
    PROP_MUTE
};

/**************************************************************
* STATIC FUNCTION PROTOTYPES                                  *
***************************************************************/
static void gst_stb22x_framed_audio_finalise (GObject * object);

static void gst_stb22x_framed_audio_set_property (GObject * object,
                                                  guint prop_id,
                                                  const GValue * value,
                                                  GParamSpec * pspec);

static void gst_stb22x_framed_audio_get_property (GObject * object,
                                                  guint prop_id,
                                                  GValue * value,
                                                  GParamSpec * pspec);

static GstStateChangeReturn gst_stb22x_framed_audio_change_state (GstElement * element,
                                                                  GstStateChange transition);

static GstFlowReturn gst_stb22x_framed_audio_preroll (GstBaseSink * bsink,
                                                      GstBuffer * buffer);

static GstFlowReturn gst_stb22x_framed_audio_render (GstBaseSink * bsink,
                                                     GstBuffer * buffer);

static gboolean gst_stb22x_framed_audio_event (GstBaseSink * bsink,
                                               GstEvent * event);

static gboolean gst_stb22x_framed_audio_start (GstBaseSink * bsink);

static gboolean gst_stb22x_framed_audio_stop (GstBaseSink * bsink);

static gboolean gst_stb22x_framed_audio_plugin_init (GstPlugin * plugin);

/**************************************************************
* STATIC DATA                                                 *
***************************************************************/

/**
 * Declare a debug category for this component
 * Enabled by doing 'export GST_DEBUG=stb22xframedaudio:x' where x=1-5.
 */
GST_DEBUG_CATEGORY (gst_stb22x_framed_audio_debug);
#define GST_CAT_DEFAULT gst_stb22x_framed_audio_debug

/**
 * Details for the IN Pin, i.e what encoded audio formats do we support?
 */
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE("sink",
                                                                    GST_PAD_SINK,
                                                                    GST_PAD_ALWAYS,
                                                                    GST_STATIC_CAPS
                                                                    ("audio/x-ac3, "
                                                                     "channels = (int) [ 1, 6]; "
                                                                     "audio/x-dts, "
                                                                     "channels = (int) [ 1, 6]; "
                                                                     "audio/x-private1-dts, "
                                                                     "channels = (int) [ 1, 6]; "
                                                                     "audio/mpeg, "
                                                                     "mpegversion = (int) { 1, 2, 4}, "
                                                                     "layer = (int) { 1, 2, 3}, "
                                                                     "channels = (int) [ 1, 6]; "
                                                                     "audio/x-wma, "
                                                                     "channels = (int) [ 1, 6], "
                                                                     "wmaversion = (int) { 1, 2, 3}; "
                                                                     "audio/x-raw-int, "
                                                                     "signed = (boolean) TRUE, "
                                                                     "width = (int) [ 16, 32], "
                                                                     "depth = (int) [ 16, 32], "
                                                                     "channels = (int) [ 1, 6]; "));
/**
 * Standard GStreamer boilerplate code generation.
 */
GST_BOILERPLATE(GstStb22xFramedAudio, gst_stb22x_framed_audio, GstVideoSink, GST_TYPE_VIDEO_SINK);

/**
 * LPCM start code as described in Design in guide.
 */
static guint8 lpcm_start_code[] = {0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA};

/**************************************************************
* FUNCTION IMPLEMENTATION                                     *
***************************************************************/
/* Local function to deal with parital writes:
   This code comes from:
       http://inn.eyrie.org/svn/branches/2.4/lib/xwrite.c

   Copyright (c) 2004, 2005, 2006, 2007, 2008, 2009
       by Internet Systems Consortium, Inc. ("ISC")
   Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
       2002, 2003 by The Internet Software Consortium and Rich Salz

   This code is derived from software contributed to the Internet Software
   Consortium by Rich Salz.

   Permission to use, copy, modify, and distribute this software for any
   purpose with or without fee is hereby granted, provided that the above
   copyright notice and this permission notice appear in all copies.

   THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
   REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY
   SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
ssize_t xwritev(int fd, const struct iovec iov[], int iovcnt)
{
    ssize_t total, status = 0;
    size_t left, offset;
    int iovleft, i, count;
    struct iovec *tmpiov;

    if (iovcnt == 0)
        return (0);

    /* Get a count of the total number of bytes in the iov array. */
    for (total = 0, i = 0; i < iovcnt; i++)
        total += iov[i].iov_len;

    if (total == 0)
        return (0);

    /* First, try just writing it all out.  Most of the time this will
       succeed and save us lots of work.  Abort the write if we try ten times
       with no forward progress. */
    count = 0;
    do
    {
        if (++count > 10)
            break;
        status = writev(fd, iov, iovcnt);
        if (status > 0)
            count = 0;
    } while (status < 0 && errno == EINTR);
    if (status < 0)
        return (-1);
    if (status == total)
        return (total);

    /* If we fell through to here, the first write partially succeeded.
       Figure out how far through the iov array we got, and then duplicate
       the rest of it so that we can modify it to reflect how much we manage
       to write on successive tries. */
    offset = status;
    left = total - offset;
    for (i = 0; offset >= (size_t) iov[i].iov_len; i++)
        offset -= iov[i].iov_len;
    iovleft = iovcnt - i;
    tmpiov = malloc(iovleft * sizeof(struct iovec));
    memcpy(tmpiov, iov + i, iovleft * sizeof(struct iovec));

    /* status now contains the offset into the first iovec struct in tmpiov.
       Go into the write loop, trying to write out everything remaining at
       each point.  At the top of the loop, status will contain a count of
       bytes written out at the beginning of the set of iovec structs. */
    i = 0;
    do
    {
        if (++count > 10)
            break;

        /* Skip any leading data that has been written out. */
        for (; offset >= (size_t) tmpiov[i].iov_len && iovleft > 0; i++)
        {
            offset -= tmpiov[i].iov_len;
            iovleft--;
        }
        tmpiov[i].iov_base = (char *) tmpiov[i].iov_base + offset;
        tmpiov[i].iov_len -= offset;

        /* Write out what's left and return success if it's all written. */
        status = writev(fd, tmpiov + i, iovleft);
        if (status <= 0)
            offset = 0;
        else
        {
            offset = status;
            left -= offset;
            count = 0;
        }
    } while (left > 0 && (status >= 0 || errno == EINTR));

    /* We're either done or got an error; if we're done, left is now 0. */
    free(tmpiov);
    return(left == 0) ? total : -1;
}

static gboolean gst_stb22x_framed_audio_write_data(GstStb22xFramedAudio *stb22x_framed_audio,
                                                   GstBuffer *payload)
{
    guint size;
    gint written;
    guint8 *data;
    guint64 buffer_time = 0;
    guint64 buffer_duration = 0;
    struct iovec iov[2];

    GST_LOG(" ->> Entering");

    size = GST_BUFFER_SIZE (payload);
    data = GST_BUFFER_DATA (payload);
    buffer_time = GST_BUFFER_TIMESTAMP (payload);
    buffer_duration = GST_BUFFER_DURATION (payload);

    /* Fill in the meta data header values that need updating for
       every frame:
       - convert timestamps into PTS format.*/
    if(buffer_time != GST_CLOCK_TIME_NONE)
    {
        stb22x_framed_audio->metaData.timeValid = True;
        stb22x_framed_audio->metaData.timeLo = (guint32) (GSTTIME_TO_MPEGTIME(buffer_time) & 0xFFFFFFFF);
        stb22x_framed_audio->metaData.timeHi = ((guint32) (GSTTIME_TO_MPEGTIME(buffer_time) >> 32) & 0x1);
    }
    else
    {
        stb22x_framed_audio->metaData.timeValid = False;
        stb22x_framed_audio->metaData.timeLo = 0;
        stb22x_framed_audio->metaData.timeHi = 0;
    }
    /* Fill in the paylaod size that we will send next.*/
    stb22x_framed_audio->metaData.payLoadLength = size;

    /* Now write the meta data and payload*/
    if (stb22x_framed_audio->metaData.payLoadLength > 0)
    {
        iov[0].iov_base = &(stb22x_framed_audio->metaData);
        iov[0].iov_len =  sizeof(stb22x_framed_audio->metaData);
        iov[1].iov_base = data;
        iov[1].iov_len = size;

        written = xwritev(stb22x_framed_audio->fd, iov, (sizeof(iov) / sizeof(struct iovec)));
        if(written < 0)
        {
            /* An error occured can use errno. */
            GST_ERROR("Failed to write metadata to stb22x_framed_audio (fd=%d)=> %s", stb22x_framed_audio->fd, strerror(errno));
        }
        /* Are we debugging and capturing the debug Meta Data file?*/
        if((stb22x_framed_audio->dump) && (stb22x_framed_audio->dump_fd != -1))
        {
            written = xwritev(stb22x_framed_audio->dump_fd, iov, (sizeof(iov) / sizeof(struct iovec)));
            if(written < 0)
            {
                /* An error occured can use errno. */
                GST_ERROR("Failed to write metadata to stb22x_framed_audio (fd=%d)=> %s", stb22x_framed_audio->fd, strerror(errno));
            }
        }
    }

    GST_LOG(" <<- Leaving");
    return TRUE;
}

/**
 * Useful error strings
 */
static GType gst_stb22x_framed_audio_state_error_get_type (void)
{
    static GType stb22xframedaudio_state_error_type = 0;
    static const GEnumValue stb22xframedaudio_state_error[] =
    {
        {STB22X_FRAMED_AUDIO_STATE_ERROR_NONE, "No state change errors", "none"},
        {STB22X_FRAMED_AUDIO_STATE_ERROR_NULL_READY, "Fail state change from NULL to READY", "null-to-ready"},
        {STB22X_FRAMED_AUDIO_STATE_ERROR_READY_PAUSED, "Fail state change from READY to PAUSED", "ready-to-paused"},
        {STB22X_FRAMED_AUDIO_STATE_ERROR_PAUSED_PLAYING, "Fail state change from PAUSED to PLAYING", "paused-to-playing"},
        {STB22X_FRAMED_AUDIO_STATE_ERROR_PLAYING_PAUSED, "Fail state change from PLAYING to PAUSED", "playing-to-paused"},
        {STB22X_FRAMED_AUDIO_STATE_ERROR_PAUSED_READY, "Fail state change from PAUSED to READY", "paused-to-ready"},
        {STB22X_FRAMED_AUDIO_STATE_ERROR_READY_NULL, "Fail state change from READY to NULL", "ready-to-null"},
        {0, NULL, NULL},
    };

    if (!stb22xframedaudio_state_error_type)
    {
        stb22xframedaudio_state_error_type = g_enum_register_static ("GstStb22xFramedAudioStateError",
                                                                     stb22xframedaudio_state_error);
    }
    return (stb22xframedaudio_state_error_type);
}

/**
 * Called on exit so make sure we tidy up after ourselves.
 * Close all open fds etc.
 */
static void gst_stb22x_framed_audio_finalise (GObject * object)
{
    GstStb22xFramedAudio *stb22x_framed_audio = GST_STB22X_FRAMED_AUDIO (object);

    GST_LOG(" ->> Entering");

    /* Make sure we close the devices.*/
    if(stb22x_framed_audio->dump_fd >= 0)
    {
        close(stb22x_framed_audio->dump_fd);
        stb22x_framed_audio->dump_fd = -1;
    }
    if(stb22x_framed_audio->fd >= 0)
    {
        close(stb22x_framed_audio->fd);
        stb22x_framed_audio->fd = -1;
    }
    G_OBJECT_CLASS (parent_class)->finalize (object);

    GST_LOG(" <<- Leaving");
}

/**
 * Get the caps of this pin.
 */
static GstCaps *gst_stb22x_framed_audio_getcaps(GstBaseSink * basesink)
{
    GstStb22xFramedAudio *stb22x_framed_audio;

    stb22x_framed_audio = GST_STB22X_FRAMED_AUDIO(basesink);

    return gst_caps_copy(gst_pad_get_pad_template_caps(GST_BASE_SINK_PAD(stb22x_framed_audio)));
}

/**
 * Set the caps (and allow me to store the mime type of what is being played.
 */
static gboolean gst_stb22x_framed_audio_setcaps(GstBaseSink * basesink,
                                                GstCaps * caps)
{
    GstStb22xFramedAudio *stb22x_framed_audio;
    GstStructure *structure;
    GstCaps *intersection;
    const gchar *mime;

    GST_LOG(" ->> Entering");

    stb22x_framed_audio = GST_STB22X_FRAMED_AUDIO(basesink);

    /* Set to Guard before starting.*/
    stb22x_framed_audio->currentCodec = GstStb22xFramedAudio_Guard;

    /* See if there are any common audio formats in the list asked for and the list we have installed.*/
    intersection = gst_caps_intersect(gst_static_caps_get(&sinktemplate.static_caps), caps);
    /* If the lists when compared are empty then we have no matching formats so will
       exit with unsupported.*/
    if (gst_caps_is_empty(intersection))
    {
        /*Do nothing.*/
    }
    else
    {
        /* Tidy up......*/
        gst_caps_unref(intersection);

        structure = gst_caps_get_structure(caps, 0);
        mime = gst_structure_get_name(structure);

        GST_INFO("Trying to match mimetype %s", mime);

        /* Check version numbers.*/
        if (!strcmp("audio/x-ac3", mime))
        {
            int channels = 0;
            gst_structure_get_int(structure, "channels", &channels);

            GST_INFO("Trying to play AC3 Number Channels=%d", channels);

            stb22x_framed_audio->currentCodec = GstStb22xFramedAudio_Ac3;
            (void)snprintf(stb22x_framed_audio->dump_filename, GST_STB22X_FRAMED_AUDIO_FILE_NAME_LENGTH, "/opt/nxp/share/media/audiodump.es_a_ac3");
        }
        else if (!strcmp("audio/mpeg", mime))
        {
            int version = 0;
            int layer = 0;
            int channels = 0;

            gst_structure_get_int(structure, "mpegversion", &version);
            gst_structure_get_int(structure, "layer", &layer);
            gst_structure_get_int(structure, "channels", &channels);

            if(version == 1)
            {
                GST_INFO("Trying to play MPEG%d layer %d version=%d Number Channels=%d", version, layer, channels);
                switch(layer)
                {
                    case 1:
                        stb22x_framed_audio->currentCodec = GstStb22xFramedAudio_Mpeg;
                        (void)snprintf(stb22x_framed_audio->dump_filename, GST_STB22X_FRAMED_AUDIO_FILE_NAME_LENGTH, "/opt/nxp/share/media/audiodump.es_a_mpa");
                        break;

                    case 3:
                    default:
                        stb22x_framed_audio->currentCodec = GstStb22xFramedAudio_Mp3;
                        (void)snprintf(stb22x_framed_audio->dump_filename, GST_STB22X_FRAMED_AUDIO_FILE_NAME_LENGTH, "/opt/nxp/share/media/audiodump.es_a_mp3");
                        break;
                }
            }
            else
            {
                const GValue *value;
                GstBuffer* codec_data = NULL;
                GstBuffer* aac_dsi = NULL;

                /* MPEG4 AAC.*/
                GST_INFO("Trying to play MPEG%d AAC Number Channels=%d", version, layer, channels);
                stb22x_framed_audio->currentCodec = GstStb22xFramedAudio_Mpeg4Aac;
                (void)snprintf(stb22x_framed_audio->dump_filename, GST_STB22X_FRAMED_AUDIO_FILE_NAME_LENGTH, "/opt/nxp/share/media/audiodump.es_a_aac");

                /* Now get the codec data and see if we can generate an ADIF header from it.*/
                value = gst_structure_get_value(structure, "codec_data");
                if(!value)
                {
                    GST_ERROR("No codec_data and we are trying to play MPEG4 AAC\n");
                }
                codec_data = gst_value_get_buffer(value);
                if(GST_BUFFER_SIZE(codec_data) > 2)
                {
                    /* We will cut the codec_data down to 2 bytes. Seems to be all we need.*/
                    aac_dsi = gst_buffer_create_sub(codec_data, 0, 2);
                    gst_buffer_unref(codec_data);
                }
                else
                {
                    aac_dsi = codec_data;
                }

                /* We need to generate an AAC header so that the decoders knwo what to do.....*/
                if(!gst_stb22x_framed_audio_generate_adif_header(stb22x_framed_audio, aac_dsi, &stb22x_framed_audio->audioDSI))
                {
                    GST_ERROR("Error creating ADIF header for MPEG4 AAC playback\n");
                }
                else
                {
                    int i = 0;
                    GST_INFO("ADIF header has been created - %d bytes long\n", GST_BUFFER_SIZE(stb22x_framed_audio->audioDSI));

                    for(i=0; i < GST_BUFFER_SIZE(stb22x_framed_audio->audioDSI); i++)
                    {
                        GST_DEBUG("0x%02x", GST_BUFFER_DATA(stb22x_framed_audio->audioDSI)[i]);
                    }
                    stb22x_framed_audio->audioDSISent = FALSE;
                }

            }
        }
        else if (!strcmp("audio/x-wma", mime))
        {
            const GValue *value;
            int version = 0;
            int channels = 0;
            int rate = 0;
            int depth = 0;
            int bitrate = 0;
            int block_align = 0;

            gst_structure_get_int(structure, "channels", &channels);
            gst_structure_get_int(structure, "wmaversion", &version);
            gst_structure_get_int(structure, "rate", &rate);
            gst_structure_get_int(structure, "depth", &depth);
            gst_structure_get_int(structure, "bitrate", &bitrate);
            gst_structure_get_int(structure, "block_align", &block_align);

            GST_INFO("Trying to play WMA version=%d Number Channels=%d rate=%d depth=%d bitrate=%d", version, channels, rate, depth, bitrate);
            stb22x_framed_audio->currentCodec = GstStb22xFramedAudio_Wma;
            (void)snprintf(stb22x_framed_audio->dump_filename, GST_STB22X_FRAMED_AUDIO_FILE_NAME_LENGTH, "/opt/nxp/share/media/audiodump.es_a_wma");

            /* We need codec_data for WMA.  This needs some parsing to make our system happy and I have no idea what it is.*/
            value = gst_structure_get_value(structure, "codec_data");
            if(!value)
            {
                GST_ERROR("No codec_data and we are trying to play WMA\n");
            }
            else
            {
                guint8* ptr;
                guint32* ptr32;
                GstBuffer* codec_data = gst_value_get_buffer(value);

                /* We have a 28 byte header.*/
                stb22x_framed_audio->audioDSI = gst_buffer_new_and_alloc(28);
                ptr = GST_BUFFER_DATA(stb22x_framed_audio->audioDSI);
                ptr32 = (guint32*)GST_BUFFER_DATA(stb22x_framed_audio->audioDSI);
                /*
                 * I have worked out that the header format is as follows, it
                 * may not be perfect but it works for the streams I have tried.
                 * The header format is as follows:
                 * 32 bits: 0x61 0x01 | 16 bit num channels
                 * 32 bits: sample rate (44100 etc)
                 * 32 bits: byte rate (bitrate / 8)
                 * 32 bits: block_align | 16 bit depth
                 * 16 bits: 0x00 0x00 (no idea what this is.)
                 * Then add the codec_data supplied by the asfdemux.
                 */
                /* clear the data.*/
                memset(ptr, 0x0, GST_BUFFER_SIZE(stb22x_framed_audio->audioDSI));
                /* Magic start code*/
                ptr[0] = 0x61;
                ptr[1] = 0x01;
                ptr[2] = channels;
                ptr[3] = 0x00;
                ptr32++;
                /* Now samplerate.*/
                *ptr32 = rate;
                ptr32++;
                /* Bitrate. Well actually byte rate. */
                *ptr32 = (bitrate / 8);
                ptr32++;
                /* Now put sample width in and the value block_align.*/
                *ptr32 = (block_align | (depth << 16));
                ptr32++;
                /* Copy in the real code_data from ASF demux now.*/
                memcpy((ptr+18), GST_BUFFER_DATA(codec_data), GST_BUFFER_SIZE(codec_data));
                stb22x_framed_audio->audioDSISent = FALSE;
            }
        }
        else if (!strcmp("audio/x-raw-int", mime))
        {
            int endianness = 0;
            int width = 0;
            int depth = 0;
            int rate = 0;
            int channels = 0;
            guint8* ptr;
            guint32* samplePtr;
            guint8 endian_chnl_allocation = 0;

            gst_structure_get_int(structure, "endianness", &endianness);
            gst_structure_get_int(structure, "width", &width);
            gst_structure_get_int(structure, "depth", &depth);
            gst_structure_get_int(structure, "rate", &rate);
            gst_structure_get_int(structure, "channels", &channels);

            GST_INFO("Trying to play LPCM endianness=%d width=%d depth=%d rate=%d channels=%d", endianness, width, depth, rate, channels);
            stb22x_framed_audio->currentCodec = GstStb22xFramedAudio_Lpcm;
            (void)snprintf(stb22x_framed_audio->dump_filename, GST_STB22X_FRAMED_AUDIO_FILE_NAME_LENGTH, "/opt/nxp/share/media/audiodump.es_a_lpcm");

            /* We need some DSI for LPCM playback DSI is 12 bytes see DIG.
            typedef struct
            {
                uint16_t  sync1;        // Sync word = 0xAA55
                uint16_t  sync2;        // Sync word = 0xAA55
                uint16_t  sync3;        // Sync word = 0xAA55
                uint32_t  samplefreq;  // Sample frequency in hertz
                uint8_t   samplesize;  // Sample Size. - valid values 16 or 32
                Bit 7 : endian Type valid values 0(big endian) or 1(little endian)
                Bit 0-6 : Channel Allocation (0x00 = Stereo, 0x0B = 6 Channel)
                uint8_t   endian_chnl_allocation;
                } phdspLpcm_streamHeader_t;
            */
            stb22x_framed_audio->audioDSI = gst_buffer_new_and_alloc(12);
            memcpy(GST_BUFFER_DATA(stb22x_framed_audio->audioDSI), lpcm_start_code, sizeof(lpcm_start_code));
            ptr = (GST_BUFFER_DATA(stb22x_framed_audio->audioDSI) + sizeof(lpcm_start_code));

            samplePtr = (guint32*)ptr;
            *samplePtr = rate;
            /* Increment by 4.*/
            ptr += sizeof(guint32);
            /* Store width.*/
            *ptr = width;
            ptr++;
            /* Store endianness.*/
            if(endianness == G_LITTLE_ENDIAN)
            {
                endian_chnl_allocation = ((1 << 7) | channels);
            }
            else
            {
                endian_chnl_allocation = channels;
            }
            *ptr = endian_chnl_allocation;
            stb22x_framed_audio->audioDSISent = FALSE;
        }
        else if (!strcmp("audio/x-dts", mime) ||
                 !strcmp("audio/x-private1-dts", mime))
        {
            GST_INFO("Trying to play BurstDts");

            /* Set to guard and then catch it.*/
            stb22x_framed_audio->currentCodec = GstStb22xFramedAudio_BurstDTS;
            (void)snprintf(stb22x_framed_audio->dump_filename, GST_STB22X_FRAMED_AUDIO_FILE_NAME_LENGTH, "/opt/nxp/share/media/audiodump.es_a_burstdts");
        }
        else
        {
            GST_INFO("Unable to play %s", mime);

            /* Set to guard and then catch it.*/
            stb22x_framed_audio->currentCodec = GstStb22xFramedAudio_Guard;
        }
    }

    /* We have been told what we are going to be receiving so now create a dump debug file.*/
    if((stb22x_framed_audio->dump) && (stb22x_framed_audio->currentCodec != GstStb22xFramedAudio_Guard))
    {
        GST_INFO("opening %s for ES dump\n", stb22x_framed_audio->dump_filename);
        stb22x_framed_audio->dump_fd = open (stb22x_framed_audio->dump_filename, O_CREAT | O_WRONLY, 0666);
        if (stb22x_framed_audio->dump_fd < 0)
        {
            GST_ERROR("Failed to open %s for writing", stb22x_framed_audio->dump_filename);
        }
    }
    /* Fill in the meta data header with the standard values.*/
    stb22x_framed_audio->metaData.magicStart = PH_STBESMETADATA_MAGIC;
    stb22x_framed_audio->metaData.formatValid = True;
    stb22x_framed_audio->metaData.format = CONVERT_TO_METADATA_AUDIOTYPE(stb22x_framed_audio->currentCodec);
    stb22x_framed_audio->metaData.flags = 0x0;
    stb22x_framed_audio->metaData.dimensionsValid = True;
    stb22x_framed_audio->metaData.width = 0;
    stb22x_framed_audio->metaData.height = 0;
    stb22x_framed_audio->metaData.magicEnd = PH_STBESMETADATA_MAGIC;

    GST_LOG(" <<- Leaving (Caps are %s)", ((stb22x_framed_audio->currentCodec == GstStb22xFramedAudio_Guard) ? "Not Supported" : "Supported"));
    return ((stb22x_framed_audio->currentCodec == GstStb22xFramedAudio_Guard) ? FALSE : TRUE);
}

/**
 * GStreamer registration function.
 * This registers the capabilities of the plugin (i.e Sink/Decoder etc)
 * and adds the sink pin.
 */
static void gst_stb22x_framed_audio_base_init (gpointer g_class)
{
    GstElementClass *gst_element_class = GST_ELEMENT_CLASS (g_class);

    GST_LOG(" ->> Entering");

    gst_element_class_set_details_simple (gst_element_class,
                                          "Stb22x Framed Audio",
                                          "Decoder/Audio/Device/Codec/Sink",
                                          "Generic STB22x Audio Sink",
                                          "NXP Semiconductors <ipstb-support@nxp.com>");

    gst_element_class_add_pad_template (gst_element_class, gst_static_pad_template_get (&sinktemplate));

    GST_LOG(" <<- Leaving");
}

/**
 * Setup the plugin with its callable properties and function ptrs.
 */
static void gst_stb22x_framed_audio_class_init (GstStb22xFramedAudioClass * klass)
{
    GObjectClass *gobject_class;
    GstElementClass *gstelement_class;
    GstBaseSinkClass *gstbase_sink_class;

    GST_LOG(" ->> Entering");

    gobject_class = G_OBJECT_CLASS (klass);
    gstelement_class = GST_ELEMENT_CLASS (klass);
    gstbase_sink_class = (GstBaseSinkClass *) klass;

    parent_class = g_type_class_peek_parent (klass);

    gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_stb22x_framed_audio_finalise);
    gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_stb22x_framed_audio_set_property);
    gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_stb22x_framed_audio_get_property);

    g_object_class_install_property (gobject_class, PROP_STATE_ERROR,
                                     g_param_spec_enum ("state-error", "State Error",
                                                        "Generate a state change error", GST_TYPE_STB22X_FRAMED_AUDIO_STATE_ERROR,
                                                        DEFAULT_STATE_ERROR, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (gobject_class, PROP_DUMP,
                                     g_param_spec_boolean ("dump", "Dump", "Dump buffer contents to stdout",
                                                           DEFAULT_DUMP, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (gobject_class, PROP_DUMP_ES_FILENAME,
                                     g_param_spec_string ("dump_filename", "ES Meta Dump", "Set the filename of the dump file",
                                                          "/opt/nxp/share/media/audiodump.es_a_aac", G_PARAM_READWRITE));

    g_object_class_install_property (gobject_class, PROP_SBMSINKNAME,
                                     g_param_spec_string ("sbmsinkname", "Sbm Sink Name", "Set the SBM sink name we are using",
                                                          "/dev/stbsbmsink0", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (gobject_class, PROP_MUTE,
                                     g_param_spec_boolean ("mute", "Mute", "Mute the output (throw away all data)",
                                                           DEFAULT_MUTE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
    /* Install the standard function ptrs.*/
    gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_stb22x_framed_audio_change_state);
    gstbase_sink_class->event = GST_DEBUG_FUNCPTR (gst_stb22x_framed_audio_event);
    gstbase_sink_class->preroll = GST_DEBUG_FUNCPTR (gst_stb22x_framed_audio_preroll);
    gstbase_sink_class->render = GST_DEBUG_FUNCPTR (gst_stb22x_framed_audio_render);
    gstbase_sink_class->get_caps = GST_DEBUG_FUNCPTR (gst_stb22x_framed_audio_getcaps);
    gstbase_sink_class->set_caps = GST_DEBUG_FUNCPTR (gst_stb22x_framed_audio_setcaps);
    gstbase_sink_class->start = GST_DEBUG_FUNCPTR(gst_stb22x_framed_audio_start);
    gstbase_sink_class->stop = GST_DEBUG_FUNCPTR (gst_stb22x_framed_audio_stop);

    GST_LOG(" <<- Leaving");
}

/**
 * Setup plugin with sensible defaults.
 */
static void gst_stb22x_framed_audio_init (GstStb22xFramedAudio * stb22x_framed_audio,
                                          GstStb22xFramedAudioClass * g_class)
{
    GST_LOG(" ->> Entering");

    stb22x_framed_audio->dump = DEFAULT_DUMP;
    stb22x_framed_audio->mute = DEFAULT_MUTE;
    GST_BASE_SINK (stb22x_framed_audio)->sync = DEFAULT_SYNC;
    stb22x_framed_audio->state_error = DEFAULT_STATE_ERROR;
    stb22x_framed_audio->fd = -1;
    stb22x_framed_audio->dump_fd = -1;
    stb22x_framed_audio->audioDSI = NULL;
    stb22x_framed_audio->audioDSISent = FALSE;

    /* Clear the meta data.*/
    memset(&stb22x_framed_audio->metaData, 0, sizeof(stb22x_framed_audio->metaData));

    GST_LOG(" <<- Leaving");
}

/**
 * Set the properties of this plugin.
 */
static void gst_stb22x_framed_audio_set_property (GObject * object,
                                                  guint prop_id,
                                                  const GValue * value,
                                                  GParamSpec * pspec)
{
    GstStb22xFramedAudio *stb22x_framed_audio;

    GST_LOG(" ->> Entering prop_id:%d", prop_id);

    stb22x_framed_audio = GST_STB22X_FRAMED_AUDIO (object);

    switch (prop_id)
    {
        case PROP_STATE_ERROR:
            stb22x_framed_audio->state_error = g_value_get_enum (value);
            break;

        case PROP_DUMP:
            stb22x_framed_audio->dump = g_value_get_boolean (value);
            break;

        case PROP_DUMP_ES_FILENAME:
            strncpy(stb22x_framed_audio->dump_filename, g_value_get_string (value), GST_STB22X_FRAMED_AUDIO_FILE_NAME_LENGTH);
            break;

        case PROP_SBMSINKNAME:
            strncpy(stb22x_framed_audio->fullDeviceName, g_value_get_string (value), GST_STB22X_FRAMED_AUDIO_FILE_NAME_LENGTH);
            break;

        case PROP_MUTE:
            stb22x_framed_audio->mute = g_value_get_boolean (value);
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break;
    }

    GST_LOG(" <<- Leaving");
}

/**
 * Get the value of the properties of this plugin.
 */
static void gst_stb22x_framed_audio_get_property (GObject * object,
                                                  guint prop_id,
                                                  GValue * value,
                                                  GParamSpec * pspec)
{
    GstStb22xFramedAudio *stb22x_framed_audio;

    GST_LOG(" ->> Entering prop_id=%d", prop_id);

    stb22x_framed_audio = GST_STB22X_FRAMED_AUDIO (object);

    switch (prop_id)
    {
        case PROP_STATE_ERROR:
            g_value_set_enum (value, stb22x_framed_audio->state_error);
            break;

        case PROP_DUMP:
            g_value_set_boolean (value, stb22x_framed_audio->dump);
            break;

        case PROP_DUMP_ES_FILENAME:
            GST_OBJECT_LOCK (stb22x_framed_audio);
            g_value_set_string (value, stb22x_framed_audio->dump_filename);
            GST_OBJECT_UNLOCK (stb22x_framed_audio);
            break;

        case PROP_SBMSINKNAME:
            GST_OBJECT_LOCK (stb22x_framed_audio);
            g_value_set_string (value, stb22x_framed_audio->fullDeviceName);
            GST_OBJECT_UNLOCK (stb22x_framed_audio);
            break;

        case PROP_MUTE:
            g_value_set_boolean (value, stb22x_framed_audio->mute);
            break;

        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
            break;
    }

    GST_LOG(" <<- Leaving");
}

/**
 * Plugin event handler.
 */
static gboolean gst_stb22x_framed_audio_event (GstBaseSink * bsink,
                                               GstEvent * event)
{
    const GstStructure *s;
    gchar *sstr;
    GstStb22xFramedAudio *stb22x_framed_audio = GST_STB22X_FRAMED_AUDIO(bsink);

    GST_LOG(" ->> Entering");

    if ((s = gst_event_get_structure (event)))
        sstr = gst_structure_to_string (s);
    else
        sstr = g_strdup ("");

    if(GST_EVENT_TYPE(event) == GST_EVENT_NEWSEGMENT)
    {
        /* This means we should resend the ADIF header next time we want to render.*/
        stb22x_framed_audio->audioDSISent = FALSE;
    }

    if (GST_BASE_SINK_CLASS (parent_class)->event)
    {
        GST_LOG(" ->> GST_BASE_SINK_CLASS");
        g_free (sstr);
        return (GST_BASE_SINK_CLASS (parent_class)->event (bsink, event));
    }
    else
    {
        GST_LOG(" ->> !GST_BASE_SINK_CLASS event   ******* E (type: %d, %s) %p", GST_EVENT_TYPE (event), sstr, event);
        g_free (sstr);
        return (TRUE);
    }
}

/**
 * Open our ES device so that we can feed it data.
 */
static gboolean gst_stb22x_framed_audio_start (GstBaseSink * bsink)
{
    gboolean ret = FALSE;
    GstStb22xFramedAudio *stb22x_framed_audio = GST_STB22X_FRAMED_AUDIO (bsink);

    GST_LOG(" ->> Entering");

    if (stb22x_framed_audio->fullDeviceName == NULL || stb22x_framed_audio->fullDeviceName[0] == '\0')
    {
        GST_ERROR("No file name specified for reading");
        ret = FALSE;
    }
    else
    {
        GST_INFO_OBJECT (stb22x_framed_audio, "opening file %s", stb22x_framed_audio->fullDeviceName);

        /* open the file */
        stb22x_framed_audio->fd = open (stb22x_framed_audio->fullDeviceName, O_WRONLY);
        if (stb22x_framed_audio->fd < 0)
        {
            GST_ERROR("Failed to open %s for reading", stb22x_framed_audio->fullDeviceName);
            ret = FALSE;
        }
        else
        {
            ret = TRUE;
        }
    }
    GST_LOG(" <<- Leaving");
    return ret;
}

/**
 * Close ES device and possibly the dump file.
 */
static gboolean gst_stb22x_framed_audio_stop (GstBaseSink * bsink)
{
    GstStb22xFramedAudio *stb22x_framed_audio = GST_STB22X_FRAMED_AUDIO (bsink);

    GST_LOG(" ->> Entering");

    /* close the files */
    if(stb22x_framed_audio->fd >= 0)
    {
        close (stb22x_framed_audio->fd);
        stb22x_framed_audio->fd = -1;
    }
    if(stb22x_framed_audio->dump_fd >= 0)
    {
        close (stb22x_framed_audio->dump_fd);
        stb22x_framed_audio->dump_fd = -1;
    }

    GST_LOG(" <<- Leaving");
    return (TRUE);
}

/**
 * Preroll function is required as it prerolls one buffer of data
 * If I do not implement this I will get a repeated first frame.
 */
static GstFlowReturn gst_stb22x_framed_audio_preroll (GstBaseSink * bsink,
                                                      GstBuffer * buffer)
{
    GST_LOG(" ->> Entering");

    GST_LOG(" <<- Leaving");
    return (GST_FLOW_OK);
}

/**
 * Write the input data to the ES device.
 */
static GstFlowReturn gst_stb22x_framed_audio_render (GstBaseSink * bsink,
                                                     GstBuffer * buffer)
{
    guint size;
    guint8 *data;
    guint64 buffer_time = 0;
    guint64 buffer_duration = 0;
    GstStb22xFramedAudio *stb22x_framed_audio;

    GST_LOG(" ->> Entering");

    size = GST_BUFFER_SIZE (buffer);
    data = GST_BUFFER_DATA (buffer);
    stb22x_framed_audio = GST_STB22X_FRAMED_AUDIO_CAST (bsink);
    buffer_time = GST_BUFFER_TIMESTAMP (buffer);
    buffer_duration = GST_BUFFER_DURATION (buffer);

    /* Only put data into sink if not muted.*/
    if(!stb22x_framed_audio->mute)
    {
        /* Send the audioDSI header if != NULL.*/
        if((stb22x_framed_audio->audioDSI != NULL) &&
           (!stb22x_framed_audio->audioDSISent))
        {
            GST_INFO(" Writing Audio DSI header");
            gst_stb22x_framed_audio_write_data(stb22x_framed_audio, stb22x_framed_audio->audioDSI);
            stb22x_framed_audio->audioDSISent = TRUE;
        }
        gst_stb22x_framed_audio_write_data(stb22x_framed_audio, buffer);
    }

    GST_LOG(" <<- Leaving");
    return (GST_FLOW_OK);
}

/**
 * Plugin State handler - handles state changes PAUSE->PLAY etc.
 */
static GstStateChangeReturn gst_stb22x_framed_audio_change_state (GstElement * element,
                                                                  GstStateChange transition)
{
    GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
    GstStb22xFramedAudio *stb22x_framed_audio = GST_STB22X_FRAMED_AUDIO (element);

    GST_LOG(" ->> Entering");

    switch (transition)
    {
        case GST_STATE_CHANGE_NULL_TO_READY:
            if (stb22x_framed_audio->state_error == STB22X_FRAMED_AUDIO_STATE_ERROR_NULL_READY)
                goto error;
            break;

        case GST_STATE_CHANGE_READY_TO_PAUSED:
            if (stb22x_framed_audio->state_error == STB22X_FRAMED_AUDIO_STATE_ERROR_READY_PAUSED)
                goto error;
            break;

        case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
            if (stb22x_framed_audio->state_error == STB22X_FRAMED_AUDIO_STATE_ERROR_PAUSED_PLAYING)
                goto error;
            break;

        default:
            break;
    }

    ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);

    switch (transition)
    {
        case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
            if (stb22x_framed_audio->state_error == STB22X_FRAMED_AUDIO_STATE_ERROR_PLAYING_PAUSED)
                goto error;
            break;

        case GST_STATE_CHANGE_PAUSED_TO_READY:
            if (stb22x_framed_audio->state_error == STB22X_FRAMED_AUDIO_STATE_ERROR_PAUSED_READY)
                goto error;
            break;

        case GST_STATE_CHANGE_READY_TO_NULL:
            if (stb22x_framed_audio->state_error == STB22X_FRAMED_AUDIO_STATE_ERROR_READY_NULL)
                goto error;
            GST_OBJECT_LOCK (stb22x_framed_audio);
            GST_OBJECT_UNLOCK (stb22x_framed_audio);
            break;

        default:
            break;
    }

    GST_LOG(" <<- Leaving");
    return (ret);

    /* ERROR */
error:
    GST_ELEMENT_ERROR (element,
                       CORE,
                       STATE_CHANGE,
                       (NULL), ("Erroring out on state change as requested"));

    GST_LOG(" <<- Leaving");
    return (GST_STATE_CHANGE_FAILURE);
}

/* ============================================================= */
/*                       Public Methods                          */
/* ============================================================= */
/**
 * Register the plugin and make it the most important sink there is.
 */
static gboolean gst_stb22x_framed_audio_plugin_init (GstPlugin * plugin)
{
    if (!gst_element_register (plugin,
                               "stb22xframedaudio",
                               GST_RANK_PRIMARY,
                               GST_TYPE_STB22X_FRAMED_AUDIO))
        return FALSE;

    GST_DEBUG_CATEGORY_INIT (gst_stb22x_framed_audio_debug, "stb22xframedaudio", 0, "stb22xframedaudio element");
    return TRUE;
}

/**
 * GST_PLUGIN_DEFINE:
 * @major: major version number of the gstreamer-core that plugin was compiled for
 * @minor: minor version number of the gstreamer-core that plugin was compiled for
 * @name: short, but unique name of the plugin
 * @description: information about the purpose of the plugin
 * @init: function pointer to the plugin_init method with the signature of <code>static gboolean plugin_init (GstPlugin * plugin)</code>.
 * @version: full version string (e.g. VERSION from config.h)
 * @license: under which licence the package has been released, e.g. GPL, LGPL.
 * @package: the package-name (e.g. PACKAGE_NAME from config.h)
 * @origin: a description from where the package comes from (e.g. the homepage URL)
 *
 * This macro needs to be used to define the entry point and meta data of a
 * plugin. One would use this macro to export a plugin, so that it can be used
 * by other applications
 */
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
                   GST_VERSION_MINOR,
                   "stb22xframedaudio",
                   "Take ES input data and add Metadata for STB225 Playback",
                   gst_stb22x_framed_audio_plugin_init,
                   VERSION,
                   "LGPL",
                   PACKAGE_NAME,
                   "NXP Semiconductors <ipstb-support@nxp.com>")

