/*
 * Copyright 2010 Trident Microsystems (Far East) Ltd. All rights reserved.
 * All prior copyright rights in this work and the accompanying software
 * products transferred to Trident Microsystems (Far East) Ltd. by written
 * agreement.
 */
/*****************************************************************************/
/* Copyright (c) 2010 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   20091203    mpage       First Version
--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

/**************************************************************
* INCLUDE FILES                                               *
***************************************************************/
#define _GNU_SOURCE
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <gst/gst.h>
#include <gst/gstclock.h>
#include <glib.h>
#include <stbMediaDemux.h>
#include "sem.h"

#define PH_STB_DBG_ENABLED (1)
#include <phStbDbg.h>

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

/** Maximum number of media demuxes. */
#define MEDIA_DEMUX_MAX_INSTANCES   (8)

/** Audio demux output pad. */
#define MEDIA_DEMUX_AUDIO_PAD       (0)
/** Video demux output pad. */
#define MEDIA_DEMUX_VIDEO_PAD       (1)
/** Max pads. */
#define MEDIA_DEMUX_MAX_PADS        (2)

/** Number of separate buffers between demux output and sink. */
#define MEDIA_DEMUX_NUM_SINK_BUFFERS (64)

/** Maximum length of a file extension.  See gMediaDemux_CodecTypeFileExtensions. */
#define MEDIA_DEMUX_MAX_EXTENSION_LENGTH    (20)

/** URL source is a file. */
#define MEDIA_DEMUX_URL_FILE        ("file://")
/** URL source is HTTP server. */
#define MEDIA_DEMUX_URL_HTTP        ("http://")

/** Fiel URL prefix length. */
#define MEDIA_DEMUX_URL_FILE_PREFIX_LENGTH  (7)

/** Checks that an instance is valid. */
#define MEDIA_DEMUX_CHECK_INSTANCE(x)   if ((x) >= MEDIA_DEMUX_MAX_INSTANCES) \
                                        { \
                                            err = STB_MEDIA_DEMUX_ERR_INVALID_INSTANCE; \
                                            goto FuncReturn; \
                                        }

/** Checks if a value is NULL and takes appropriate actions. */
#define MEDIA_DEMUX_CHECK_NOT_NULL(x, y)    if ((x) == NULL) { DBG_PRINT2(gMediaDemux_DbgUnit, DBG_WARNING, "NULL Failure in %s at %d", __FUNCTION__, __LINE__); \
                                                               err = (y); goto FuncReturn; }

/** Links two elements together. */
#define MEDIA_DEMUX_LINK(a, b, c)   if (!gst_element_link((a), (b))) \
                                    { \
                                        err = (c); \
                                        goto FuncReturn; \
                                    }

/** Changes an element's state. */
#define MEDIA_DEMUX_STATE_CHANGE(a, b, c)   stateChangeReturn = gst_element_set_state((a), (b)); \
                                            if (mediaDemux_CheckStateChangeReturn(pMediaDemux, stateChangeReturn, __LINE__)) \
                                            { \
                                                if ((c) != STB_MEDIA_DEMUX_ERR_OK) \
                                                { \
                                                    err = (c); \
                                                    goto FuncReturn; \
                                                } \
                                            }

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

/** Media Demux object. */
typedef struct mediaDemux
{
    /** Indicates whether this object is in use. */
    int inUse;

    /** The URL being demuxed. */
    char url[STB_MEDIA_DEMUX_MAX_FILENAME_LENGTH];

    /** Thread handle. */
    pthread_t thread;

    /** GStreamer objects and elements. */
    GMainLoop *pLoop;
    GstElement *pPipeline;
    GstBus *pBus;
    GstTagList *pTags;
    GstElement *pSrc;
    GstElement *pSrcQueue;
    GstElement *pDemux;
    GstElement *pAudioQueue;
    GstElement *pAudioSink;
    GstElement *pVideoQueue;
    GstElement *pVideoSink;
    GstElement *pFramer;

    /** Demux output pads. */
    stbMediaDemux_CodecType_t outputCodecs[MEDIA_DEMUX_MAX_PADS];

    /** Callback function and user data. */
    stbMediaDemux_EventCallback_t pEventCallbackFunc;
    unsigned int userData;

    /** Synchronisation object. */
    psem_t  pEvent;
} mediaDemux_t;

/**************************************************************
* STATIC FUNCTION PROTOTYPES                                  *
***************************************************************/

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

/** Array of demux onjects. */
static mediaDemux_t gMediaDemux_Instances[MEDIA_DEMUX_MAX_INSTANCES];

/** Debug unit. */
DBG_UNIT(gMediaDemux_DbgUnit)

/** Demux type names (indicates container). */
static char* gMediaDemux_DemuxTypeNames[] =
{
    "None",
    "AVI",
    "QT",
    "MPEGPS",
    "MPEGTS",
    "FLV",
    "MKV",
    "ASF",
    "Guard"
};

/** Codec type names. */
static char* gMediaDemux_CodecTypeNames[] =
{
    "None",
    "VIDEO MPEG1",
    "VIDEO MPEG2",
    "VIDEO MPEG4",
    "VIDEO H263",
    "VIDEO H264",
    "VIDEO DIVX3",
    "VIDEO WMV",
    "VIDEO VP6",
    "AUDIO MP1",
    "AUDIO MP2",
    "AUDIO MP3",
    "AUDIO MPEG2",
    "AUDIO AAC",
    "AUDIO AC3",
    "AUDIO DTS",
    "AUDIO WMA",
    "AUDIO LPCM",
    "Guard"
};

/** Codec type names. */
static char* gMediaDemux_CodecTypeFileExtensions[] =
{
    ".none",
    ".es_v_mpeg1",
    ".es_v_mpeg2",
    ".es_v_mpeg4",
    ".es_v_h263flv",
    ".es_v_h264",
    ".es_v_divx",
    ".es_v_wmv",
    ".es_v_vp6",
    ".es_a_mpeg",
    ".es_a_mpeg",
    ".es_a_mp3",
    ".es_a_mpeg",
    ".es_a_aac",
    ".es_a_ac3",
    ".es_a_burstdts",
    ".es_a_wma",
    ".es_a_lpcm",
    ".unknown"
};

/** Codec type names. */
static char* gMediaDemux_CommandTypeNames[] =
{
    "None",
    "Start",
    "Stop",
    "GetDuration",
    "GetPosition",
    "Seek",
    "FastForward",
    "Rewind",
    "Guard"
};

/**************************************************************
* STATIC FUNCTION IMPLEMENTATIONS                             *
***************************************************************/

/******************************************************************************************************/
/** Returns the next available free demux object for use. */
static int mediaDemux_FindFree(void)
{
    int i;

    for (i=0;i<MEDIA_DEMUX_MAX_INSTANCES;i++)
    {
        if (!gMediaDemux_Instances[i].inUse)
        {
            break;
        }
    }

    return i;
}

#if 0
/******************************************************************************************************/
/** Enables/Disables all debug. */
static void mediaDemux_EnableDebug(unsigned int enable)
{
    if (enable)
    {
        gst_debug_set_default_threshold(GST_LEVEL_LOG);
        gst_debug_set_active(TRUE);
    }
    else
    {
        gst_debug_set_active(FALSE);
    }
}
#endif

/******************************************************************************************************/
/** Checks a state change return. */
static unsigned int mediaDemux_CheckStateChangeReturn(mediaDemux_t* pMediaDemux, GstStateChangeReturn stateChangeReturn, unsigned int lineNumber)
{
    unsigned int failure = FALSE;

    /* Did we fail to start playing, if so why?????*/
    if (stateChangeReturn == GST_STATE_CHANGE_FAILURE)
    {
        GstMessage *pMsg;

        DBG_PRINT2(gMediaDemux_DbgUnit, DBG_WARNING, "ERROR Failed to start up pipeline!: %s, %d", strrchr(__FILE__, '/') + 1, lineNumber);

        /* check if there is an error message with details on the bus */
        pMsg = gst_bus_poll(pMediaDemux->pBus, GST_MESSAGE_ERROR, 0);
        if (pMsg)
        {
            GError *pBusErr = NULL;
            gst_message_parse_error (pMsg, &pBusErr, NULL);
            DBG_PRINT3(gMediaDemux_DbgUnit, DBG_WARNING, "ERROR from gstreamer message='%s'!: %s, %d", pBusErr->message, strrchr(__FILE__, '/') + 1, lineNumber);
            g_error_free (pBusErr);
            gst_message_unref (pMsg);
        }

        failure = TRUE;
    }

    return failure;
}


/******************************************************************************************************/
/** Identifies the audio codec type from GstStructure. */
static stbMediaDemux_CodecType_t mediaDemux_IdentifyCodecType(GstStructure *pStructure)
{
    const gchar *pMime;
    stbMediaDemux_CodecType_t codecType = stbMediaDemux_CodecType_Guard;

    DBG_PRINT0(gMediaDemux_DbgUnit, DBG_INTERNAL_ENTER, "mediaDemux_IdentifyCodecType");

    pMime = gst_structure_get_name(pStructure);

    if (!strcmp("video/x-divx", pMime))
    {
        int version = 0;
        gst_structure_get_int(pStructure, "divxversion", &version);

        DBG_PRINT1(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Setting up to play DIVX version=%d", version);

        switch(version)
        {
            case 3:
                codecType = stbMediaDemux_CodecType_VideoDIVX3;
                break;

            case 4:
            case 5:
                codecType = stbMediaDemux_CodecType_VideoMPEG4;
                break;

            default:
                codecType = stbMediaDemux_CodecType_Guard;
                break;
        }
    }
    else if (!strcmp("video/x-xvid", pMime))
    {
        DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Setting up to play XVID");

        codecType = stbMediaDemux_CodecType_VideoMPEG4;
    }
    else if (!strcmp("video/x-msmpeg", pMime))
    {
        int version = 0;
        gst_structure_get_int(pStructure, "msmpegversion", &version);

        DBG_PRINT1(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Setting up to play MS-MPEG version=%d", version);

        switch(version)
        {
            case 41:
            case 42:
            case 43:
                codecType = stbMediaDemux_CodecType_VideoMPEG4;
                break;

            default:
                codecType = stbMediaDemux_CodecType_Guard;
                break;
        }
    }
    else if (!strcmp("video/mpeg", pMime))
    {
        int version = 0;
        gst_structure_get_int(pStructure, "mpegversion", &version);

        DBG_PRINT1(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Setting up to play MPEG version=%d", version);

        switch(version)
        {
            case 1:
                codecType = stbMediaDemux_CodecType_VideoMPEG1;
                break;

            case 2:
                codecType = stbMediaDemux_CodecType_VideoMPEG2;
                break;

            case 4:
                codecType = stbMediaDemux_CodecType_VideoMPEG4;
                break;

            default:
                codecType = stbMediaDemux_CodecType_Guard;
                break;
        }
    }
    else if (!strcmp("video/x-h263", pMime))
    {
        const gchar* h263version = gst_structure_get_string(pStructure, "h263version");
        const gchar* h263variant = gst_structure_get_string(pStructure, "variant");

        DBG_PRINT2(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Trying to play H263 Version '%s' Variant '%s'",
                     (h263version ? h263version : "unknown"),
                     (h263variant ? h263variant : "unknown"));

        /* We tell MSVD its Mpeg4 and its seems happy so far.*/
        codecType = stbMediaDemux_CodecType_VideoMPEG4;
    }
    else if (!strcmp("video/x-h264", pMime))
    {
        DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Setting up to play H264");

        codecType = stbMediaDemux_CodecType_VideoH264;
    }
    else if (!strcmp("video/x-wmv", pMime))
    {
        int version = 0;
        guint32 fourcc;

        gst_structure_get_int(pStructure, "wmvversion", &version);
        gst_structure_get_fourcc (pStructure, "format", &fourcc);

        DBG_PRINT2(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Trying to play WMV version=%d fourcc=%" GST_FOURCC_FORMAT "", version, GST_FOURCC_ARGS (fourcc));

        switch(version)
        {
            case 1:
            case 2:
            case 3:
                codecType = stbMediaDemux_CodecType_VideoWMV;
                break;

            default:
                codecType = stbMediaDemux_CodecType_Guard;
                break;
        }
    }
    else if (!strcmp("video/x-vp6-flash", pMime))
    {
        DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Trying to play Flash VP6");
        codecType = stbMediaDemux_CodecType_VideoVP6;
    }
    else if (!strcmp("video/x-flash-video", pMime))
    {
        DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Trying to play Flash H263");
        codecType = stbMediaDemux_CodecType_VideoH263;
    }
    else if (!strcmp("audio/x-ac3", pMime))
    {
        int channels = 0;
        gst_structure_get_int(pStructure, "channels", &channels);

        DBG_PRINT1(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Trying to play AC3 Number Channels=%d", channels);
        codecType = stbMediaDemux_CodecType_AudioAC3;
    }
    else if (!strcmp("audio/mpeg", pMime))
    {
        int version = 0;
        int layer = 0;
        int channels = 0;

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

        if(version == 1)
        {
            DBG_PRINT3(gMediaDemux_DbgUnit, DBG_LEVEL_1,
                "Trying to play MPEG%d layer %d version=%d Number Channels=%d", version, layer, channels);
            switch(layer)
            {
                case 1:
                    codecType = stbMediaDemux_CodecType_Guard;
                    break;

                /* Some files fail to report their layer so I just allow
                   this and guess it is mpeg.  This may need some refinement
                   in the future.*/
                case 0:
                case 2:
                    codecType = stbMediaDemux_CodecType_AudioMPEG2;
                    break;

                case 3:
                default:
                    codecType = stbMediaDemux_CodecType_AudioMP3;
                    break;
            }
        }
        else
        {
            /* MPEG4 AAC.*/
            DBG_PRINT3(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Trying to play MPEG%d AAC Number Channels=%d", version, layer, channels);
            codecType = stbMediaDemux_CodecType_AudioAAC;
        }
    }
    else if (!strcmp("audio/x-wma", pMime))
    {
        int version = 0;
        int channels = 0;

        gst_structure_get_int(pStructure, "channels", &channels);
        gst_structure_get_int(pStructure, "wmaversion", &version);

        DBG_PRINT2(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Trying to play WMA version=%d Number Channels=%d", version, channels);
        codecType = stbMediaDemux_CodecType_AudioWMA;
    }
    else if (!strcmp("audio/x-raw-int", pMime))
    {
        int endianness = 0;
        int width = 0;
        int depth = 0;
        int rate = 0;
        int channels = 0;

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

        if(width != 0 && rate != 0 && channels != 0)
        {
            DBG_PRINT5(gMediaDemux_DbgUnit, DBG_LEVEL_1,
                "Trying to play LPCM endianness=%d width=%d depth=%d rate=%d num_channels=%d", endianness, width, depth, rate, channels);
            codecType = stbMediaDemux_CodecType_AudioLPCM;
        }
        else
        {
             DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Trying to play LPCM - Not enough information to play it!");
             codecType = stbMediaDemux_CodecType_Guard;
        }
    }
    else if (!strcmp("audio/x-private1-lpcm", pMime))
    {
         DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Trying to play DVD - LPCM, this is not currently supported");
         codecType = stbMediaDemux_CodecType_Guard;
    }
    else if (!strcmp("audio/x-dts", pMime) ||
             !strcmp("audio/x-private1-dts", pMime))
    {
        DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Trying to play BurstDts");
        codecType = stbMediaDemux_CodecType_AudioDTS;
    }
    else
    {
        DBG_PRINT1(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Unable to play %s", pMime);

        /* Set to guard and then catch it.*/
        codecType = stbMediaDemux_CodecType_Guard;
    }

    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_INTERNAL_LEAVE, "mediaDemux_IdentifyCodecType -> %s", gMediaDemux_CodecTypeNames[codecType]);
    return codecType;
}

/******************************************************************************************************/
/** GStreamer demux callback when no more pads will be added. */
static void mediaDemux_NoMorePads(GstElement *element, gpointer user_data)
{
    mediaDemux_t *pMediaDemux = (mediaDemux_t *)user_data;

    DBG_PRINT0(gMediaDemux_DbgUnit, DBG_INTERNAL_ENTER, "mediaDemux_NoMorePads");

    (void)element;

    DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "NO MORE PADS!");
    (void)event_send(pMediaDemux->pEvent);

    DBG_PRINT0(gMediaDemux_DbgUnit, DBG_INTERNAL_LEAVE, "mediaDemux_NoMorePads");
}

/******************************************************************************************************/
/** Takes the passed filename, removes its path and existing extension (if there is one) and adds the passed extension. */
static void mediaDemux_ChangeFileExtension(char* pFilename, char* pExtension, char* pNewFilename)
{
    char* pCurrentExt = NULL;
    char* pPathEnd = NULL;

    DBG_PRINT2(gMediaDemux_DbgUnit, DBG_INTERNAL_ENTER, "mediaDemux_ChangeFileExtension(filename:%s ext:%s)", pFilename, pExtension);

    /* Find the end of the path... */
    pPathEnd = strrchr(pFilename, '/');
    if (pPathEnd != NULL)
    {
        /* ...and then point to the next char. */
        pPathEnd++;
    }
    else
    {
        /* ...or set pPathEnd to start of filename. */
        pPathEnd = pFilename;
    }

    /* Check for extension. */
    pCurrentExt = strrchr(pFilename, '.');

    /* Copy from end of path up to extension if present, or rest of filename if not. */
    if (pCurrentExt != NULL)
    {
        strncpy(pNewFilename, pPathEnd, (int)(pCurrentExt - pPathEnd));
        pNewFilename[(int)(pCurrentExt - pPathEnd)] = '\0';
    }
    else
    {
        strcpy(pNewFilename, pPathEnd);
    }

    /* Add our new extension. */
    strcat(pNewFilename, pExtension);

    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_INTERNAL_LEAVE, "mediaDemux_ChangeFileExtension(new filename:%s", pNewFilename);
}

/******************************************************************************************************/
/** GStreamer demux callback when a new pad has been added to the demux. */
static void mediaDemux_OnPadAdded(GstElement *element, GstPad *pNewPad, gpointer user_data)
{
    gchar *pName;
    const gchar *pMimetype;
    GstCaps *pCaps;
    GstStructure *pStructure;
    mediaDemux_t *pMediaDemux = (mediaDemux_t *)user_data;
    stbMediaDemux_Error_t err;

    DBG_PRINT0(gMediaDemux_DbgUnit, DBG_INTERNAL_ENTER, "mediaDemux_OnPadAdded");

    (void)element;
    pName = gst_pad_get_name (pNewPad);
    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_LEVEL_3, "PAD name: %s", pName);
    pCaps = gst_pad_get_caps (pNewPad);
    if (pCaps != NULL)
    {
        if (gst_caps_get_size(pCaps) > 0)
        {
            pStructure = gst_caps_get_structure(pCaps, 0);
            if (pStructure != NULL)
            {
                GstPad *pSinkPad = NULL;
                int i;
                char outputFileName[STB_MEDIA_DEMUX_MAX_FILENAME_LENGTH + MEDIA_DEMUX_MAX_EXTENSION_LENGTH];
                stbMediaDemux_CodecInfo_t codecInfo;
                GstStateChangeReturn stateChangeReturn;

                strncpy(outputFileName, pMediaDemux->url, STB_MEDIA_DEMUX_MAX_FILENAME_LENGTH);

                pMimetype = gst_structure_get_name(pStructure);
                DBG_PRINT1(gMediaDemux_DbgUnit, DBG_LEVEL_3, "MIME type: %s", pMimetype);

                if ((g_strrstr(pMimetype, "audio")) &&
                    (pMediaDemux->outputCodecs[MEDIA_DEMUX_AUDIO_PAD] == stbMediaDemux_CodecType_None))
                {
                    pMediaDemux->outputCodecs[MEDIA_DEMUX_AUDIO_PAD] = mediaDemux_IdentifyCodecType(pStructure);
                    mediaDemux_ChangeFileExtension(pMediaDemux->url,
                        gMediaDemux_CodecTypeFileExtensions[pMediaDemux->outputCodecs[MEDIA_DEMUX_AUDIO_PAD]],
                        outputFileName);

                    codecInfo.codecType = pMediaDemux->outputCodecs[MEDIA_DEMUX_AUDIO_PAD];
                    codecInfo.pOutputFilename = outputFileName;
                    pMediaDemux->pEventCallbackFunc(pMediaDemux->userData, stbMediaDemux_Event_CodecIdentified, &codecInfo);

                    g_object_set(G_OBJECT(pMediaDemux->pAudioSink), "sbmsinkname", outputFileName, NULL);
                    pSinkPad = gst_element_get_static_pad(pMediaDemux->pAudioQueue, "sink");
                    gst_pad_link(pNewPad, pSinkPad);

                    MEDIA_DEMUX_STATE_CHANGE(pMediaDemux->pAudioQueue, GST_STATE_PLAYING, STB_MEDIA_DEMUX_ERR_OK);
                    MEDIA_DEMUX_STATE_CHANGE(pMediaDemux->pAudioSink, GST_STATE_PAUSED, STB_MEDIA_DEMUX_ERR_OK);
                }
                else if ((g_strrstr(pMimetype, "video")) &&
                         (pMediaDemux->outputCodecs[MEDIA_DEMUX_VIDEO_PAD] == stbMediaDemux_CodecType_None))
                {
                    pMediaDemux->outputCodecs[MEDIA_DEMUX_VIDEO_PAD] = mediaDemux_IdentifyCodecType(pStructure);
                    mediaDemux_ChangeFileExtension(pMediaDemux->url,
                        gMediaDemux_CodecTypeFileExtensions[pMediaDemux->outputCodecs[MEDIA_DEMUX_VIDEO_PAD]],
                        outputFileName);

                    codecInfo.codecType = pMediaDemux->outputCodecs[MEDIA_DEMUX_VIDEO_PAD];
                    codecInfo.pOutputFilename = outputFileName;
                    pMediaDemux->pEventCallbackFunc(pMediaDemux->userData, stbMediaDemux_Event_CodecIdentified, &codecInfo);

                    g_object_set(G_OBJECT(pMediaDemux->pVideoSink), "sbmsinkname", outputFileName, NULL);
                    pSinkPad = gst_element_get_static_pad(pMediaDemux->pVideoQueue, "sink");
                    gst_pad_link(pNewPad, pSinkPad);

                    MEDIA_DEMUX_STATE_CHANGE(pMediaDemux->pVideoQueue, GST_STATE_PLAYING, STB_MEDIA_DEMUX_ERR_OK);
                    MEDIA_DEMUX_STATE_CHANGE(pMediaDemux->pVideoSink, GST_STATE_PAUSED, STB_MEDIA_DEMUX_ERR_OK);
                }

                /** Check whether we've got codecs for all PADs. */
                for (i=0;i<MEDIA_DEMUX_MAX_PADS;i++)
                {
                    if (pMediaDemux->outputCodecs[i] == stbMediaDemux_CodecType_None)
                    {
                        break;
                    }
                }
                if (i == MEDIA_DEMUX_MAX_PADS)
                {
                    DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "Codecs Identified");
                    (void)event_send(pMediaDemux->pEvent);
                }
            }
        }
        gst_caps_unref (pCaps);
    }

FuncReturn:
    DBG_PRINT0(gMediaDemux_DbgUnit, DBG_INTERNAL_LEAVE, "mediaDemux_OnPadAdded");
}

/******************************************************************************************************/
/** GStreamer tag callback. */
void mediaDemux_TagCallback(const GstTagList *list, const gchar *tag, gpointer user_data)
{
    gchar *value = NULL;
    gdouble double_value = 0.0;
    guint uint_value = 0;
    guint64 uint64_value = 0;
    GDate *date;

    (void)user_data;

    if ((strcmp(GST_TAG_TITLE, tag) == 0) ||
        (strcmp(GST_TAG_ARTIST, tag) == 0) ||
        (strcmp(GST_TAG_ALBUM, tag) == 0) ||
        (strcmp(GST_TAG_GENRE, tag) == 0) ||
        (strcmp(GST_TAG_COMMENT, tag) == 0) ||
        (strcmp(GST_TAG_EXTENDED_COMMENT, tag) == 0) ||
        (strcmp(GST_TAG_LOCATION, tag) == 0) ||
        (strcmp(GST_TAG_DESCRIPTION, tag) == 0) ||
        (strcmp(GST_TAG_VERSION, tag) == 0) ||
        (strcmp(GST_TAG_ISRC, tag) == 0) ||
        (strcmp(GST_TAG_ORGANIZATION, tag) == 0) ||
        (strcmp(GST_TAG_COPYRIGHT, tag) == 0) ||
        (strcmp(GST_TAG_COPYRIGHT_URI, tag) == 0) ||
        (strcmp(GST_TAG_CONTACT, tag) == 0) ||
        (strcmp(GST_TAG_LICENSE, tag) == 0) ||
        (strcmp(GST_TAG_LICENSE_URI, tag) == 0) ||
        (strcmp(GST_TAG_PERFORMER, tag) == 0) ||
        (strcmp(GST_TAG_CODEC, tag) == 0) ||
        (strcmp(GST_TAG_VIDEO_CODEC, tag) == 0) ||
        (strcmp(GST_TAG_AUDIO_CODEC, tag) == 0) ||
        (strcmp(GST_TAG_ENCODER, tag) == 0) ||
        (strcmp(GST_TAG_LANGUAGE_CODE, tag) == 0) ||
        (strcmp(GST_TAG_CODEC, tag) == 0))
    {
        if(gst_tag_list_get_string(list, tag, &value))
        {
            if (value != NULL)
            {
                DBG_PRINT2(gMediaDemux_DbgUnit, DBG_LEVEL_4, "Tag '%s' is: %s", tag, value);
                g_free(value);
            }
        }
    }
    else if ((strcmp(GST_TAG_TRACK_NUMBER, tag) == 0) ||
             (strcmp(GST_TAG_TRACK_COUNT, tag) == 0) ||
             (strcmp(GST_TAG_ALBUM_VOLUME_NUMBER, tag) == 0) ||
             (strcmp(GST_TAG_ALBUM_VOLUME_COUNT, tag) == 0) ||
             (strcmp(GST_TAG_BITRATE, tag) == 0) ||
             (strcmp(GST_TAG_NOMINAL_BITRATE, tag) == 0) ||
             (strcmp(GST_TAG_MINIMUM_BITRATE, tag) == 0) ||
             (strcmp(GST_TAG_MAXIMUM_BITRATE, tag) == 0) ||
             (strcmp(GST_TAG_ENCODER_VERSION, tag) == 0))
    {
        if (gst_tag_list_get_uint(list, tag, &uint_value))
        {
            if (uint_value)
            {
                DBG_PRINT2(gMediaDemux_DbgUnit, DBG_LEVEL_4, "Tag '%s' is: %d", tag, uint_value);
            }
        }
    }
    else if ((strcmp(GST_TAG_TRACK_GAIN, tag) == 0) ||
             (strcmp(GST_TAG_TRACK_PEAK, tag) == 0) ||
             (strcmp(GST_TAG_ALBUM_GAIN, tag) == 0) ||
             (strcmp(GST_TAG_ALBUM_PEAK, tag) == 0) ||
             (strcmp(GST_TAG_REFERENCE_LEVEL, tag) == 0) ||
             (strcmp(GST_TAG_BEATS_PER_MINUTE, tag) == 0))
    {
        if (gst_tag_list_get_double(list, tag, &double_value))
        {
            if (double_value)
            {
                DBG_PRINT2(gMediaDemux_DbgUnit, DBG_LEVEL_4, "Tag '%s' is: %f", tag, double_value);
            }
        }
    }
    else if ((strcmp(GST_TAG_DATE, tag) == 0))
    {
        if (gst_tag_list_get_date(list, GST_TAG_DATE, &date))
        {
            gchar res[5];
            g_date_strftime (res, sizeof(res), "%Y", date);
            DBG_PRINT2(gMediaDemux_DbgUnit, DBG_LEVEL_4, "Tag '%s' is: %s", GST_TAG_DATE, value);
        }
    }
    else if ((strcmp(GST_TAG_DURATION, tag) == 0))
    {
        if (gst_tag_list_get_uint64(list, tag, &uint64_value))
        {
            if (double_value)
            {
                DBG_PRINT2(gMediaDemux_DbgUnit, DBG_LEVEL_4, "Tag '%s' is: %llu", tag, uint64_value);
            }
        }
    }
    else
    {
        DBG_PRINT1(gMediaDemux_DbgUnit, DBG_LEVEL_4, "Tag '%s' is not supported", tag);
    }
}

/******************************************************************************************************/
/** GStreamer bus callback. */
static gboolean mediaDemux_BusCall(GstBus *pBus, GstMessage *pMsg, gpointer user_data)
{
    mediaDemux_t *pMediaDemux = (mediaDemux_t *)user_data;

    (void)pBus;

    if(pMsg != NULL)
    {
        switch (GST_MESSAGE_TYPE(pMsg))
        {
            case GST_MESSAGE_EOS:
                /* Exit Gstreamer loop.*/
                DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "mediaDemux_BusCall -> EOS");
                pMediaDemux->pEventCallbackFunc(pMediaDemux->userData, stbMediaDemux_Event_EndOfStream, NULL);
                break;

            case GST_MESSAGE_ERROR:
            {
                gchar *pDebug = NULL;
                GError *pErr = NULL;

                gst_message_parse_error(pMsg, &pErr, &pDebug);
                DBG_PRINT1(gMediaDemux_DbgUnit, DBG_WARNING, "mediaDemux_BusCall -> ERROR: %s", pErr->message);
                g_error_free(pErr);
                if (pDebug)
                {
                    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_WARNING, "mediaDemux_BusCall -> ERROR, Debug details: %s", pDebug);
                    g_free(pDebug);
                }
                pMediaDemux->pEventCallbackFunc(pMediaDemux->userData, stbMediaDemux_Event_Error, NULL);
                break;
            }
            case GST_MESSAGE_ELEMENT:
            {
                break;
            }
            case GST_MESSAGE_TAG:
            {
                GstTagList *pNewTags, *pMergedTags;
                gst_message_parse_tag(pMsg, &pNewTags);

                pMergedTags = gst_tag_list_merge(pMediaDemux->pTags, pNewTags, GST_TAG_MERGE_PREPEND);
                if (pMergedTags)
                {
                    if (pMediaDemux->pTags)
                    {
                        gst_tag_list_free(pMediaDemux->pTags);
                    }
                    pMediaDemux->pTags = pMergedTags;
                }
                gst_tag_list_foreach(pMediaDemux->pTags, mediaDemux_TagCallback, NULL);
                break;
            }
            default:
                DBG_PRINT1(gMediaDemux_DbgUnit, DBG_LEVEL_5, "mediaDemux_BusCall -> %s Unhandled", GST_MESSAGE_TYPE_NAME(pMsg));
                break;
        }
    }
    return (TRUE);
}

/******************************************************************************************************/
/** Frees all currently allocated resource for this demux. */
static void mediaDemux_FreeResources(mediaDemux_t *pMediaDemux)
{
    DBG_PRINT0(gMediaDemux_DbgUnit, DBG_INTERNAL_ENTER, "mediaDemux_FreeResources");

    if (pMediaDemux->pPipeline)
    {
        (void)gst_element_set_state(pMediaDemux->pPipeline, GST_STATE_NULL);
        DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_2, "Freeing Pipeline");
        gst_object_unref(pMediaDemux->pPipeline);
        pMediaDemux->pPipeline = NULL;
    }

    /* All of the following are freed with the pipeline. */
    pMediaDemux->pSrc = NULL;
    pMediaDemux->pSrcQueue = NULL;
    pMediaDemux->pDemux = NULL;
    pMediaDemux->pFramer = NULL;
    pMediaDemux->pAudioQueue = NULL;
    pMediaDemux->pAudioSink = NULL;
    pMediaDemux->pVideoQueue = NULL;
    pMediaDemux->pVideoSink = NULL;

    (void)event_destroy(pMediaDemux->pEvent);

    DBG_PRINT0(gMediaDemux_DbgUnit, DBG_INTERNAL_LEAVE, "mediaDemux_FreeResources");
}

/******************************************************************************************************/
/** Separate thread context for GStreamer main loop. */
static void * mediaDemux_Thread(void *pArg)
{
    mediaDemux_t* pMediaDemux = (mediaDemux_t*)pArg;

    DBG_PRINT0(gMediaDemux_DbgUnit, DBG_INTERNAL_ENTER, "mediaDemux_Thread()");

    g_main_loop_run(pMediaDemux->pLoop);

    DBG_PRINT0(gMediaDemux_DbgUnit, DBG_INTERNAL_LEAVE, "mediaDemux_Thread()");

    return NULL;
}

/******************************************************************************************************/
/** Pauses/Resumes demux processing by (un)pausing sinks. */
static stbMediaDemux_Error_t mediaDemux_Pause(mediaDemux_t *pMediaDemux, unsigned int enable)
{
    stbMediaDemux_Error_t err = STB_MEDIA_DEMUX_ERR_OK;
    GstStateChangeReturn stateChangeReturn;
    GstState newState;

    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_INTERNAL_ENTER, "mediaDemux_Pause(enable:%u)", enable);

    if (enable)
    {
        newState = GST_STATE_PAUSED;
    }
    else
    {
        newState = GST_STATE_PLAYING;
    }

    if (pMediaDemux->outputCodecs[MEDIA_DEMUX_AUDIO_PAD] != stbMediaDemux_CodecType_None)
    {
        MEDIA_DEMUX_STATE_CHANGE(pMediaDemux->pAudioSink, newState, STB_MEDIA_DEMUX_ERR_STATE_AUDIO);
    }
    if (pMediaDemux->outputCodecs[MEDIA_DEMUX_VIDEO_PAD] != stbMediaDemux_CodecType_None)
    {
        MEDIA_DEMUX_STATE_CHANGE(pMediaDemux->pVideoSink, newState, STB_MEDIA_DEMUX_ERR_STATE_VIDEO);
    }

FuncReturn:
    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_INTERNAL_LEAVE, "mediaDemux_Pause() -> 0x%X", err);

    return err;
}

/******************************************************************************************************/
/** Retrieves the duration of the stream in seconds. */
static stbMediaDemux_Error_t mediaDemux_GetDuration(mediaDemux_t *pMediaDemux, unsigned int* pSeconds)
{
    stbMediaDemux_Error_t err = STB_MEDIA_DEMUX_ERR_OK;
    gboolean res = 0;
    GstQuery *pQuery;

    DBG_PRINT0(gMediaDemux_DbgUnit, DBG_INTERNAL_ENTER, "mediaDemux_GetDuration()");

    pQuery = gst_query_new_duration(GST_FORMAT_TIME);
    res = gst_element_query(pMediaDemux->pPipeline, pQuery);
    if (res)
    {
        gint64 duration = 0;
        gst_query_parse_duration(pQuery, NULL, &duration);
        *pSeconds = duration / GST_SECOND;
    }
    else
    {
        err = STB_MEDIA_DEMUX_ERR_DURATION_UNAVAILABLE;
    }
    gst_query_unref (pQuery);

    DBG_PRINT2(gMediaDemux_DbgUnit, DBG_INTERNAL_LEAVE, "mediaDemux_GetDuration(seconds:%u) -> 0x%X",
        ((err == STB_MEDIA_DEMUX_ERR_OK) ? *pSeconds : 0xFFFFFFFF), err);

    return err;
}

/******************************************************************************************************/
/** Seeks to the given point. */
static stbMediaDemux_Error_t mediaDemux_Seek(mediaDemux_t *pMediaDemux, unsigned int seconds)
{
    stbMediaDemux_Error_t err = STB_MEDIA_DEMUX_ERR_OK;
    gboolean res = 0;
    GstStateChangeReturn stateChangeReturn;
    double rate = 1.0;

    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_INTERNAL_ENTER, "mediaDemux_Seek(seconds:%u)", seconds);

    res = gst_element_seek(pMediaDemux->pPipeline,
                           rate,
                           GST_FORMAT_TIME,
                           (GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
                           GST_SEEK_TYPE_SET,
                           (seconds * GST_SECOND),
                           GST_SEEK_TYPE_NONE,
                           GST_CLOCK_TIME_NONE);

    if (res)
    {
        stateChangeReturn = gst_element_get_state(pMediaDemux->pPipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
        if (stateChangeReturn != GST_STATE_CHANGE_SUCCESS)
        {
            err = STB_MEDIA_DEMUX_ERR_SEEK;
        }
    }
    else
    {
        err = STB_MEDIA_DEMUX_ERR_SEEK;
    }

    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_INTERNAL_LEAVE, "mediaDemux_Seek() -> 0x%X", err);

    return err;
}

/******************************************************************************************************/
/** Plays at the given speed. */
static stbMediaDemux_Error_t mediaDemux_Trick(mediaDemux_t *pMediaDemux, int rate)
{
    stbMediaDemux_Error_t err = STB_MEDIA_DEMUX_ERR_OK;
    gboolean res = 0;

    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_INTERNAL_ENTER, "mediaDemux_Trick(rate:%d)", rate);

    res = gst_element_seek(pMediaDemux->pPipeline,
                           (double)rate,
                           GST_FORMAT_TIME,
                           (GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
                           GST_SEEK_TYPE_NONE,
                           GST_CLOCK_TIME_NONE,
                           GST_SEEK_TYPE_NONE,
                           GST_CLOCK_TIME_NONE);

    if (!res)
    {
        err = STB_MEDIA_DEMUX_ERR_TRICK;
    }

    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_INTERNAL_LEAVE, "mediaDemux_Trick() -> 0x%X", err);

    return err;
}

/******************************************************************************************************/
/** Allocates a source component for the given url. */
static stbMediaDemux_Error_t mediaDemux_AllocateSource(mediaDemux_t *pMediaDemux, char* url)
{
    stbMediaDemux_Error_t err = STB_MEDIA_DEMUX_ERR_OK;
    unsigned int filenameOffset = 0;

    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_INTERNAL_ENTER, "mediaDemux_AllocateSource(url:%s)", url);

    if (strcasestr(url, MEDIA_DEMUX_URL_FILE))
    {
        pMediaDemux->pSrc = gst_element_factory_make("filesrc", "source");
        /* For file sources we remove the URL prefix if its there. */
        filenameOffset = MEDIA_DEMUX_URL_FILE_PREFIX_LENGTH;
        DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "mediaDemux_AllocateSource() Selecting File Source");
    }
    else if (strcasestr(url, MEDIA_DEMUX_URL_HTTP))
    {
        pMediaDemux->pSrc = gst_element_factory_make("neonhttpsrc", "source");
        MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pSrc , STB_MEDIA_DEMUX_ERR_ALLOC_SOURCE);
        g_object_set(G_OBJECT(pMediaDemux->pSrc), "blocksize", 256*1024, NULL);
        g_object_set(G_OBJECT(pMediaDemux->pSrc), "neon-http-debug", TRUE, NULL);
        DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "mediaDemux_AllocateSource() Selecting HTTP Source");
        pMediaDemux->pSrcQueue = gst_element_factory_make("queue", "srcQueue");
        MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pSrc, STB_MEDIA_DEMUX_ERR_ALLOC_SOURCE_QUEUE);
        g_object_set(G_OBJECT(pMediaDemux->pSrcQueue), "max-size-buffers", 16, NULL);
        g_object_set(G_OBJECT(pMediaDemux->pSrcQueue), "max-size-bytes", 256*1024, NULL);
        g_object_set(G_OBJECT(pMediaDemux->pSrcQueue), "max-size-time", (guint64)0, NULL);
    }
    else
    {
        /* Default to file source. */
        pMediaDemux->pSrc = gst_element_factory_make("filesrc", "source");
        DBG_PRINT0(gMediaDemux_DbgUnit, DBG_LEVEL_1, "mediaDemux_AllocateSource() DEFAULT Selecting File Source");
    }
    MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pSrc, STB_MEDIA_DEMUX_ERR_ALLOC_SOURCE);

    /* Copy the url (revised if necessary). */
    strncpy(pMediaDemux->url, url + filenameOffset, STB_MEDIA_DEMUX_MAX_FILENAME_LENGTH);

    /* Set the URL. */
    g_object_set(G_OBJECT(pMediaDemux->pSrc), "location", pMediaDemux->url, NULL);

FuncReturn:
    DBG_PRINT2(gMediaDemux_DbgUnit, DBG_INTERNAL_LEAVE, "mediaDemux_AllocateSource(url:%s) -> 0x%X", pMediaDemux->url, err);

    return err;
}

/**************************************************************
* EXTERNAL FUNCTION IMPLEMENTATIONS                           *
***************************************************************/

/******************************************************************************************************/
stbMediaDemux_Error_t stbMediaDemux_Create(char* url,
                                           stbMediaDemux_Type_t demuxType,
                                           unsigned int detectionTimeout,
                                           stbMediaDemux_EventCallback_t pEventCallbackFunc,
                                           unsigned int userData,
                                           stbMediaDemux_t* pDemux)
{
    stbMediaDemux_Error_t err = STB_MEDIA_DEMUX_ERR_OK;
    stbMediaDemux_t demux;
    mediaDemux_t* pMediaDemux = NULL;
    GstStateChangeReturn stateChangeReturn;
    int i;

    DBG_PRINT3(gMediaDemux_DbgUnit, DBG_INTERFACE_ENTER, "stbMediaDemux_Create(url:%s type:%s timeout:%u)",
        url, gMediaDemux_DemuxTypeNames[demuxType], detectionTimeout);

    demux = mediaDemux_FindFree();

    if (demux >= MEDIA_DEMUX_MAX_INSTANCES)
    {
        err = STB_MEDIA_DEMUX_ERR_NO_INSTANCES;
        goto FuncReturn;
    }
    pMediaDemux = &gMediaDemux_Instances[demux];
    pMediaDemux->inUse = TRUE;
    pMediaDemux->pPipeline = NULL;
    pMediaDemux->pSrc = NULL;
    pMediaDemux->pSrcQueue = NULL;
    pMediaDemux->pDemux = NULL;
    pMediaDemux->pFramer = NULL;
    pMediaDemux->pAudioQueue = NULL;
    pMediaDemux->pAudioSink = NULL;
    pMediaDemux->pVideoQueue = NULL;
    pMediaDemux->pVideoSink = NULL;
    for (i=0;i<MEDIA_DEMUX_MAX_PADS;i++)
    {
        pMediaDemux->outputCodecs[i] = stbMediaDemux_CodecType_None;
    }

    if (pEventCallbackFunc == NULL)
    {
        err = STB_MEDIA_DEMUX_ERR_NULL_CALLBACK;
        goto FuncReturn;
    }
    pMediaDemux->pEventCallbackFunc = pEventCallbackFunc;
    pMediaDemux->userData = userData;

    /* initialization */
    gst_init(NULL, NULL);
    pMediaDemux->pLoop = g_main_loop_new(NULL, FALSE);
    MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pLoop, STB_MEDIA_DEMUX_ERR_ALLOC_MAINLOOP);

    if (detectionTimeout > 0)
    {
        /* create sync object. */
        if (event_create(&pMediaDemux->pEvent) != 0)
        {
            err = STB_MEDIA_DEMUX_ERR_ALLOC_SYNC_OBJECT;
            goto FuncReturn;
        }
    }

    /* create elements */
    pMediaDemux->pPipeline = gst_pipeline_new("pipeline");
    MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pPipeline, STB_MEDIA_DEMUX_ERR_ALLOC_PIPELINE);

    /* watch for messages on the pipeline's bus (note that this will only
     * work like this when a GLib main loop is running) */
    pMediaDemux->pBus = gst_pipeline_get_bus(GST_PIPELINE(pMediaDemux->pPipeline));
    MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pBus, STB_MEDIA_DEMUX_ERR_ALLOC_BUS);
    gst_bus_add_watch(pMediaDemux->pBus, mediaDemux_BusCall, pMediaDemux);
    gst_object_unref(pMediaDemux->pBus);

    /* We need a source.*/
    err = mediaDemux_AllocateSource(pMediaDemux, url);
    if (err != STB_MEDIA_DEMUX_ERR_OK)
    {
        goto FuncReturn;
    }

    if (demuxType == stbMediaDemux_Type_None)
    {
        // TODO: Setup typefind mechanism...
    }

    if (demuxType == stbMediaDemux_Type_None)
    {
        err = STB_MEDIA_DEMUX_ERR_UNRECOGNISED_CONTAINER;
        goto FuncReturn;
    }

    /* Which demux are we trying to use. These may or may not be available*/
    switch (demuxType)
    {
        case stbMediaDemux_Type_AVI:
            /** We need an AVI demux. (gst-plugins-good) */
            pMediaDemux->pDemux = gst_element_factory_make("avidemux", "demux");
            break;
        case stbMediaDemux_Type_QT:
            /** We need the qtdemux. (gst-plugins-good) */
            pMediaDemux->pDemux = gst_element_factory_make("qtdemux", "demux");
            break;
        case stbMediaDemux_Type_MPEGPS:
            /** We need an Mpeg demux. (gst-plugins-bad )*/
            pMediaDemux->pDemux = gst_element_factory_make("mpegdemux", "demux");
            //pMediaDemux->pFramer = gst_element_factory_make("mpegvideoparse", "framer");
            //MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pFramer, STB_MEDIA_DEMUX_ERR_ALLOC_FRAMER);
            break;
        case stbMediaDemux_Type_MPEGTS:
            /** We need an Mpeg demux. (gst-plugins-bad )*/
            pMediaDemux->pDemux = gst_element_factory_make("mpegdemux", "demux");
            MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pDemux, STB_MEDIA_DEMUX_ERR_ALLOC_DEMUX);
            //g_object_set(G_OBJECT(pMediaDemux->pDemux), "check-crc", FALSE, NULL);
            //pMediaDemux->pFramer = gst_element_factory_make("mpegvideoparse", "framer");
            //MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pFramer, STB_MEDIA_DEMUX_ERR_ALLOC_FRAMER);
            break;
        case stbMediaDemux_Type_FLV:
            /** We need an flv demux. (gst-plugins-bad)*/
            pMediaDemux->pDemux  = gst_element_factory_make("flvdemux", "demux");
            break;
        case stbMediaDemux_Type_MKV:
            /** We need the Matrovska demux. (gst-plugins-good) */
            pMediaDemux->pDemux  = gst_element_factory_make("matroskademux", "demux");
            break;
        case stbMediaDemux_Type_ASF:
            /** We need the ASF demux. (gst-plugins-ugly) - Check you are happy with the licensing! */
            pMediaDemux->pDemux  = gst_element_factory_make("asfdemux", "demux");
            break;
        default:
            break;
    }
    MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pDemux, STB_MEDIA_DEMUX_ERR_ALLOC_DEMUX);

    /* Create output queues and sinks. */
    pMediaDemux->pAudioQueue = gst_element_factory_make ("queue", "audioQueue");
    MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pAudioQueue, STB_MEDIA_DEMUX_ERR_ALLOC_AUDIO_QUEUE);
    g_object_set(G_OBJECT(pMediaDemux->pAudioQueue), "max-size-buffers", MEDIA_DEMUX_NUM_SINK_BUFFERS, NULL);
    g_object_set(G_OBJECT(pMediaDemux->pAudioQueue), "max-size-bytes", 0, NULL);
    g_object_set(G_OBJECT(pMediaDemux->pAudioQueue), "max-size-time", (guint64)0, NULL);

    pMediaDemux->pVideoQueue = gst_element_factory_make ("queue", "videoQueue");
    MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pVideoQueue, STB_MEDIA_DEMUX_ERR_ALLOC_AUDIO_QUEUE);
    g_object_set(G_OBJECT(pMediaDemux->pVideoQueue), "max-size-buffers", MEDIA_DEMUX_NUM_SINK_BUFFERS, NULL);
    g_object_set(G_OBJECT(pMediaDemux->pVideoQueue), "max-size-bytes", 0, NULL);
    g_object_set(G_OBJECT(pMediaDemux->pVideoQueue), "max-size-time", (guint64)0, NULL);

    pMediaDemux->pAudioSink = gst_element_factory_make("stb22xframedaudio", "audioSink");
    MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pAudioSink, STB_MEDIA_DEMUX_ERR_ALLOC_AUDIO_SINK);

    pMediaDemux->pVideoSink = gst_element_factory_make("stb22xframedvideo", "videoSink");
    MEDIA_DEMUX_CHECK_NOT_NULL(pMediaDemux->pVideoSink, STB_MEDIA_DEMUX_ERR_ALLOC_VIDEO_SINK);

    /* Add elements into pipeline. */
    gst_bin_add_many(GST_BIN(pMediaDemux->pPipeline),
        pMediaDemux->pSrc, pMediaDemux->pDemux, pMediaDemux->pAudioQueue, pMediaDemux->pAudioSink,
        pMediaDemux->pVideoQueue, pMediaDemux->pVideoSink, NULL);

    if (pMediaDemux->pSrcQueue != NULL)
    {
        gst_bin_add_many(GST_BIN(pMediaDemux->pPipeline), pMediaDemux->pSrcQueue, NULL);
        MEDIA_DEMUX_LINK(pMediaDemux->pSrc, pMediaDemux->pSrcQueue, STB_MEDIA_DEMUX_ERR_LINK_SOURCE_QUEUE);
        MEDIA_DEMUX_LINK(pMediaDemux->pSrcQueue, pMediaDemux->pDemux, STB_MEDIA_DEMUX_ERR_LINK_SOURCE_DEMUX);
    }
    else
    {
        MEDIA_DEMUX_LINK(pMediaDemux->pSrc, pMediaDemux->pDemux, STB_MEDIA_DEMUX_ERR_LINK_SOURCE_DEMUX);
    }

    MEDIA_DEMUX_LINK(pMediaDemux->pAudioQueue, pMediaDemux->pAudioSink, STB_MEDIA_DEMUX_ERR_LINK_AUDIO_QUEUE_SINK);
    MEDIA_DEMUX_LINK(pMediaDemux->pVideoQueue, pMediaDemux->pVideoSink, STB_MEDIA_DEMUX_ERR_LINK_VIDEO_QUEUE_SINK);

    /* Add a handler that can connect the demux pins dynamically to the queue pins.*/
    g_signal_connect(pMediaDemux->pDemux, "pad-added", G_CALLBACK(mediaDemux_OnPadAdded), pMediaDemux);

    /* Add a handler that says no more pads.*/
    g_signal_connect(pMediaDemux->pDemux, "no-more-pads", G_CALLBACK(mediaDemux_NoMorePads), pMediaDemux);

    /* Start the main loop. */
    {
        int pthreadErr;
        pthreadErr = pthread_create(&pMediaDemux->thread, NULL, mediaDemux_Thread, (void*)pMediaDemux);
        if (pthreadErr < 0)
        {
            DBG_PRINT2(gMediaDemux_DbgUnit, DBG_WARNING, "ERROR Failed to start up pipeline!: %s, %d", strrchr(__FILE__, '/') + 1, __LINE__);
            err = STB_MEDIA_DEMUX_ERR_ALLOC_MAIN_LOOP_THREAD;
        }
    }

    MEDIA_DEMUX_STATE_CHANGE(pMediaDemux->pDemux, GST_STATE_PLAYING, STB_MEDIA_DEMUX_ERR_STATE_DEMUX);
    if (pMediaDemux->pSrcQueue != NULL)
    {
        MEDIA_DEMUX_STATE_CHANGE(pMediaDemux->pSrcQueue, GST_STATE_PLAYING, STB_MEDIA_DEMUX_ERR_STATE_SOURCE_QUEUE);
    }
    MEDIA_DEMUX_STATE_CHANGE(pMediaDemux->pSrc, GST_STATE_PLAYING, STB_MEDIA_DEMUX_ERR_STATE_SOURCE);

    if (detectionTimeout > 0)
    {
        if (event_waitWithTimeout(pMediaDemux->pEvent, detectionTimeout, 0) != 0)
        {
            DBG_PRINT0(gMediaDemux_DbgUnit, DBG_WARNING, "stbMediaDemux_Create() Detection Timeout");
        }
    }

    *pDemux = demux;

FuncReturn:

    if (err != STB_MEDIA_DEMUX_ERR_OK)
    {
        mediaDemux_FreeResources(pMediaDemux);
        pMediaDemux->inUse = FALSE;
    }

    DBG_PRINT2(gMediaDemux_DbgUnit, DBG_INTERFACE_LEAVE, "stbMediaDemux_Create(demux:%u) -> 0x%X", *pDemux, err);
    return err;
}

/******************************************************************************************************/
stbMediaDemux_Error_t stbMediaDemux_Command(stbMediaDemux_t demux, stbMediaDemux_Command_t* pCommand)
{
    stbMediaDemux_Error_t err = STB_MEDIA_DEMUX_ERR_OK;
    mediaDemux_t* pMediaDemux;

    DBG_PRINT2(gMediaDemux_DbgUnit, DBG_INTERFACE_ENTER, "stbMediaDemux_Command(demux:%u command:%s)",
        demux, gMediaDemux_CommandTypeNames[pCommand->type]);

    MEDIA_DEMUX_CHECK_INSTANCE(demux);

    pMediaDemux = &gMediaDemux_Instances[demux];

    switch (pCommand->type)
    {
        case stbMediaDemux_CommandType_Start:
            err = mediaDemux_Pause(pMediaDemux, FALSE);
            break;
        case stbMediaDemux_CommandType_Stop:
            err = mediaDemux_Pause(pMediaDemux, TRUE);
            break;
        case stbMediaDemux_CommandType_GetDuration:
            err = mediaDemux_GetDuration(pMediaDemux, (unsigned int*)pCommand->pData);
            break;
        case stbMediaDemux_CommandType_Seek:
            err = mediaDemux_Seek(pMediaDemux, (unsigned int)pCommand->pData);
            break;
        case stbMediaDemux_CommandType_FastForward:
            err = mediaDemux_Trick(pMediaDemux, (int)pCommand->pData);
            break;
        case stbMediaDemux_CommandType_Rewind:
            err = mediaDemux_Trick(pMediaDemux, (int)pCommand->pData);
            break;
        default:
            break;
    }

FuncReturn:
    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_INTERFACE_LEAVE, "stbMediaDemux_Command() -> 0x%X", err);
    return err;
}

/******************************************************************************************************/
stbMediaDemux_Error_t stbMediaDemux_Destroy(stbMediaDemux_t demux)
{
    stbMediaDemux_Error_t err = STB_MEDIA_DEMUX_ERR_OK;
    mediaDemux_t* pMediaDemux;
    int waitIterations = 0;

    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_INTERFACE_ENTER, "stbMediaDemux_Destroy(demux:%u)", demux);

    MEDIA_DEMUX_CHECK_INSTANCE(demux);

    pMediaDemux = &gMediaDemux_Instances[demux];
    g_main_loop_quit(pMediaDemux->pLoop);
    while (g_main_loop_is_running(pMediaDemux->pLoop) && (waitIterations < 5))
    {
        DBG_PRINT0(gMediaDemux_DbgUnit, DBG_WARNING, "stbMediaDemux_Destroy() main loop still running, waiting");
        waitIterations++;
        sleep(1);
    }
    //mediaDemux_EnableDebug(FALSE);
    mediaDemux_FreeResources(pMediaDemux);
    pMediaDemux->inUse = FALSE;

FuncReturn:
    DBG_PRINT1(gMediaDemux_DbgUnit, DBG_INTERFACE_LEAVE, "stbMediaDemux_Destroy() -> 0x%X", err);
    return err;
}

