#include "omx_tool.h"

#define VERBOSE_ON 1
#define VERBOSE2_ON 1

#ifdef VERBOSE_ON
    #define DBG_PRINT(...) fprintf(stdout, __VA_ARGS__)
#else
    #define DBG_PRINT(...)
#endif

#if defined(VERBOSE_ON) && defined(VERBOSE2_ON)
    #define DBG_PRINT2(...) fprintf(stdout, __VA_ARGS__)
#else
    #define DBG_PRINT2(...)
#endif

#ifdef VERBOSE_ON
    #ifdef _WIN32_WCE
        #define STDOUT_FILE "\\Storage Card\\stdout.txt"
        #define STDERR_FILE "\\Storage Card\\stderr.txt"
    #endif
#endif

#define NOTSET_U8 ((OMX_U8)0xDE)
#define NOTSET_U16 ((OMX_U16)0xDEDE)
#define NOTSET_U32 ((OMX_U32)0xDEDEDEDE)
#define INIT_PARAM(_X_)  (memset(&(_X_), NOTSET_U8, sizeof(_X_)), ((_X_).nSize = sizeof (_X_)), (_X_).nVersion = vOMX)

void OMXInstance_UpdateJavaAttributes(OMXToolBasicAV_t *pOMXAV);

static void DestroyInstanceUnlock(OMXToolBasicAV_t * pOMXAV);

#define OMXSAFEVOID(x) \
do { \
    OMX_ERRORTYPE err = (x); \
    if (err != OMX_ErrorNone) { \
        JoglCommon_throwNewRuntimeException(0, "FAILED at %s:%d, Error: 0x%x\n", __FILE__, __LINE__, err); \
        if(NULL!=pOMXAV) { \
            DestroyInstanceUnlock(pOMXAV); \
        } \
        return; \
    } \
} while (0);

#define OMXSAFE(x) \
do { \
    OMX_ERRORTYPE err = (x); \
    if (err != OMX_ErrorNone) { \
        JoglCommon_throwNewRuntimeException(0, "FAILED at %s:%d, Error: 0x%x\n", __FILE__, __LINE__, err); \
        if(NULL!=pOMXAV) { \
            DestroyInstanceUnlock(pOMXAV); \
        } \
        return -1; \
    } \
} while (0);

#define OMXSAFEERR(x) \
do { \
    OMX_ERRORTYPE err = (x); \
    if (err != OMX_ErrorNone) { \
        JoglCommon_throwNewRuntimeException(0, "FAILED at %s:%d, Error: 0x%x\n", __FILE__, __LINE__, err); \
        if(NULL!=pOMXAV) { \
            DestroyInstanceUnlock(pOMXAV); \
        } \
        return err; \
    } \
} while (0);

static PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR;
static PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;
static PFNEGLGETSYNCATTRIBKHRPROC eglGetSyncAttribKHR;
static PFNEGLSIGNALSYNCKHRPROC eglSignalSyncKHR;
static int _hasEGLSync = 0;

#define GETEXTENSION(type, ext) \
do \
{ \
    ext = (type) eglGetProcAddress(#ext); \
    if (!ext) \
    { \
        fprintf(stderr, "ERROR getting proc addr of " #ext "\n"); \
    } \
} while (0);

int USE_OPENGL  = 1;
int USE_HWAUDIOOUT = 1;
int USE_AUDIOBUFFERING = 0;
const int PORT_VRENDERER = 6;

static OMX_VERSIONTYPE vOMX;

static int _initialized = 0;
static void InitStatic()
{
    if(_initialized) return;

#ifdef VERBOSE_ON
    #ifdef _WIN32_WCE
        _wfreopen(TEXT(STDOUT_FILE),L"w",stdout);
        _wfreopen(TEXT(STDERR_FILE),L"w",stderr);
    #endif
#endif

    _initialized = 1;

    vOMX.s.nVersionMajor = 1;
    vOMX.s.nVersionMinor = 1;
    vOMX.s.nRevision = 0;
    vOMX.s.nStep = 0;

    GETEXTENSION(PFNEGLCREATEIMAGEKHRPROC,      eglCreateImageKHR);
    GETEXTENSION(PFNEGLCREATESYNCKHRPROC,       eglCreateSyncKHR);
    GETEXTENSION(PFNEGLGETSYNCATTRIBKHRPROC,    eglGetSyncAttribKHR);
    GETEXTENSION(PFNEGLSIGNALSYNCKHRPROC,       eglSignalSyncKHR);
    if(NULL==eglGetSyncAttribKHR||NULL==eglSignalSyncKHR) {
        _hasEGLSync = 0;
    } else {
        _hasEGLSync = 1;
    }

    OMX_Init();
}

static void Invalidate(OMXToolBasicAV_t * pOMXAV)
{
    DBG_PRINT("INVALIDATE\n");
    pOMXAV->status=OMXAV_INVALID;
}

static void GetComponentName(OMX_HANDLETYPE hComponent, KDchar *pName, int nameMaxLen)
{
    OMX_VERSIONTYPE v1, v2;
    OMX_UUIDTYPE uuid;

    OMX_GetComponentVersion(hComponent, pName, &v1, &v2, &uuid);
}

static OMX_ERRORTYPE UpdateStreamInfo(OMXToolBasicAV_t * pOMXAV, KDboolean issueCallback);

static OMX_ERRORTYPE EventHandler(
        OMX_IN OMX_HANDLETYPE hComponent,
        OMX_IN OMX_PTR pAppData,
        OMX_IN OMX_EVENTTYPE eEvent,
        OMX_IN OMX_U32 nData1,
        OMX_IN OMX_U32 nData2,
        OMX_IN OMX_PTR pEventData)
{
    OMXToolBasicAV_t * pOMXAV = (OMXToolBasicAV_t *) pAppData;
    KDchar name[128];

    GetComponentName(hComponent, name, 128);

    switch (eEvent)
    {
        case OMX_EventCmdComplete:
        {
            DBG_PRINT("event complete: cmd 0x%X, s:0x%X, component: %p - %s\n", (unsigned)nData1, (unsigned)nData2, hComponent, name);
            if (nData1 == OMX_CommandStateSet && pOMXAV->status == OMXAV_INVALID)
            {
                if (nData2 > OMX_StateLoaded) {
                    DBG_PRINT("\t state -> StateLoaded\n");
                    // Transition the component down to StateLoaded
                    OMX_SendCommand(hComponent, OMX_CommandStateSet, OMX_StateLoaded, 0);
                }
            }
            else if (nData1 == OMX_CommandFlush && nData2 == OMX_ALL)
            {
                DBG_PRINT("\t flush\n");
                kdThreadSemPost(pOMXAV->flushSem);
            }
            break;
        }
        case OMX_EventBufferFlag:
            if (nData2 & OMX_BUFFERFLAG_EOS)
            {
                DBG_PRINT("event buffer EOS: component: %p - %s\n", hComponent, name);
                if (pOMXAV->endComponent == hComponent)
                {
                    DBG_PRINT("\t end component - FIN\n");
                    pOMXAV->status = OMXAV_FIN;
                }
            }
            break;
        case OMX_EventError: 
            {
                if (nData1 == OMX_ErrorIncorrectStateTransition)
                {
                    DBG_PRINT("event error: 0x%X IncorrectTransition, component: %p - %s\n", (unsigned int) nData1, hComponent, name);
                    // We are shutting down, just continue with that process
                    OMX_SendCommand(hComponent, OMX_CommandStateSet, OMX_StateIdle, 0);
                }
                else if(nData1 == OMX_ErrorSameState) 
                {
                    DBG_PRINT("event error: Same State 0x%X, component: %p - %s\n", (unsigned int) nData2, hComponent, name);
                } 
                else
                {
                    DBG_PRINT("event error: 0x%X, component: %p - %s\n", (unsigned int) nData1, hComponent, name);
                    Invalidate(pOMXAV);
                }
            } 
            break;
        case OMX_EventPortSettingsChanged:
            {
                (void) UpdateStreamInfo(pOMXAV, (pOMXAV->status>OMXAV_INIT)?KD_TRUE:KD_FALSE);
            }
            break;
        default:
            break;
    }

    return OMX_ErrorNone;
}


static OMX_ERRORTYPE EmptyBufferDone(
        OMX_OUT OMX_HANDLETYPE hComponent,
        OMX_OUT OMX_PTR pAppData,
        OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer)
{
    return OMX_ErrorNone;
}

static OMX_ERRORTYPE FillBufferDone(
        OMX_OUT OMX_HANDLETYPE hComponent,
        OMX_OUT OMX_PTR pAppData,
        OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer)
{
    OMXToolBasicAV_t *pOMXAV = (OMXToolBasicAV_t *) pAppData;

    if (pBuffer->nFlags & OMX_BUFFERFLAG_EOS)
    {
        pOMXAV->status = OMXAV_FIN;
    }
    pOMXAV->available++;
    DBG_PRINT("FillBufferDone avail %d\n", pOMXAV->available);

    return OMX_ErrorNone;
}

#define STATE_SLEEP     10  // ms
#define STATE_TIMEOUT 1000  // ms
#define STATE_TIMEOUT_LOOP (STATE_TIMEOUT/STATE_SLEEP)

static OMX_ERRORTYPE WaitForState(OMX_HANDLETYPE hComponent,
                           OMX_STATETYPE eTestState,
                           OMX_STATETYPE eTestState2,
                           OMX_STATETYPE *currentState)
{
    OMX_ERRORTYPE eError = OMX_ErrorNone;
    OMX_STATETYPE eState;
    int loop=STATE_TIMEOUT_LOOP;

    DBG_PRINT( "WaitForState p1 c:%p s1:0x%X s2:0x%X\n", hComponent, eTestState, eTestState2);
    eError = OMX_GetState(hComponent, &eState);
    DBG_PRINT( "WaitForState p2 s:0x%X e:0x%X\n", eState, eError);

    while (loop>0 &&
           OMX_ErrorNone == eError &&
           eState != eTestState && 
           eState != eTestState2) 
    {
        usleep(STATE_SLEEP*1000);
        loop--;

        eError = OMX_GetState(hComponent, &eState);
        DBG_PRINT( "WaitForState p3 s:0x%X e:0x%X\n", eState, eError);
    }

    if(NULL!=currentState) *currentState=eState;

    return eError;
}

static KDint SyncOnState(OMX_HANDLETYPE hComponent, OMX_STATETYPE state)
{
    OMX_STATETYPE currentState;
    OMX_ERRORTYPE eError = WaitForState(hComponent, state, OMX_StateInvalid, &currentState);
    return ( OMX_ErrorNone != eError ) ? -1 : ( currentState!=state ) ? -2 : 0 ;
}

static KDint CheckState(OMX_HANDLETYPE hComponent, OMX_STATETYPE state)
{
    OMX_ERRORTYPE eError = OMX_ErrorNone;
    OMX_STATETYPE eState;

    eError = OMX_GetState(hComponent, &eState);

    return ( OMX_ErrorNone != eError ) ? -1 : ( eState!=state ) ? -2 : 0 ;
}

KDint OMXToolBasicAV_IsFileValid(const KDchar * file)
{ 
    #ifdef _WIN32
        KDchar cvtdPath[_MAX_PATH];

        if(NULL==file) return -1;

        kdStrcpy_s(cvtdPath, _MAX_PATH, file);
        while(kdStrchr(cvtdPath,'/'))
            *kdStrchr(cvtdPath,'/')='\\';

        {
        #ifdef UNICODE
            wchar_t properfilename[_MAX_PATH];
            mbstowcs( properfilename, cvtdPath, _MAX_PATH );
        #else
            char *properfilename = cvtdPath;
        #endif

            if (INVALID_FILE_ATTRIBUTES==GetFileAttributes(properfilename))
            {
                fprintf(stderr, "!>Input file (%s) does not exist!  EXITING.", file);
                return -2;
            }
        }
    #else
        if(NULL==file) return -1;
    #endif

    return 0;
}

KDint OMXToolBasicAV_CheckState(OMXToolBasicAV_t * pOMXAV, OMX_STATETYPE state)
{
    KDint i, res;
    if(NULL==pOMXAV) return -1;

    for(i=0; i<OMXAV_H_NUMBER; i++) {
        if(0!=pOMXAV->comp[i]) {
            if( 0!=(res=CheckState(pOMXAV->comp[i], state)) ) {
                return res-(i*10); 
            }
        }
    }
    return 0;
}

KDint OMXToolBasicAV_WaitForState(OMXToolBasicAV_t * pOMXAV, OMX_STATETYPE state)
{
    KDint res, i;
    DBG_PRINT( "OMXToolBasicAV_WaitForState %p s:%d\n", pOMXAV, state);
    if(NULL==pOMXAV) {
        DBG_PRINT( "OMXToolBasicAV_WaitForState p1\n");
        return -1;
    }

    for(i=0; i<OMXAV_H_NUMBER; i++) {
        if(0!=pOMXAV->comp[i]) {
            DBG_PRINT( "OMXToolBasicAV_WaitForState p4 %d c:%p\n", i, pOMXAV->comp[i]);
            if( 0!=(res=SyncOnState(pOMXAV->comp[i], state)) ) {
                KDchar name[128];
                GetComponentName(pOMXAV->comp[i], name, 128);
                DBG_PRINT( "OMXToolBasicAV_WaitForState Failed (Wait) %d c:%p - %s, s:0x%X\n", 
                    i, pOMXAV->comp[i], name, state); 
                return res-(i*10); 
            }
        }
    }

    return 0;
}

static OMX_ERRORTYPE RequestState(OMX_HANDLETYPE hComponent, OMX_STATETYPE state, KDboolean wait) 
{
    OMX_ERRORTYPE eError = OMX_ErrorNone;
    OMX_STATETYPE eState;
    eError = OMX_GetState(hComponent, &eState);
    DBG_PRINT( "RequestState p2 c:%p, e:0x%X, s:0x%X\n", 
        hComponent, eError, eState);
    // Skip StateSet in case the state is already reached ..
    if(OMX_ErrorNone != eError || eState!=state) {
        eError = OMX_SendCommand(hComponent, OMX_CommandStateSet, state, 0);
        DBG_PRINT( "RequestState p3 c:%p e:0x%X s: 0x%X -> 0x%X\n", 
            hComponent, eError, eState, state);
        if(wait) {
            OMX_STATETYPE currentState;
            eError = WaitForState(hComponent, state, OMX_StateInvalid, &currentState);
            if ( OMX_ErrorNone==eError && currentState!=state ) eError=OMX_StateInvalid;
        }
    }
    return eError;
}

KDint OMXToolBasicAV_RequestState(OMXToolBasicAV_t * pOMXAV, OMX_STATETYPE state, KDboolean wait)
{
    KDint i;
    DBG_PRINT( "OMXToolBasicAV_RequestState %p s:%d, w:%d\n", pOMXAV, state, wait);
    if(NULL==pOMXAV) {
        DBG_PRINT( "OMXToolBasicAV_RequestState p1\n");
        return -1;
    }

    for(i=0; i<OMXAV_H_NUMBER; i++) {
        if(0!=pOMXAV->comp[i]) {
            OMXSAFE(RequestState(pOMXAV->comp[i], state, KD_FALSE));
        }
    }

    if (wait)
    {
        return OMXToolBasicAV_WaitForState(pOMXAV, state);
    }

    return 0;
}

static KDint SendCommand(OMXToolBasicAV_t * pOMXAV, OMX_COMMANDTYPE cmd, OMX_U32 nParam1, OMX_PTR pCmdData)
{
    KDint i;
    if(NULL==pOMXAV) return -1;

    for(i=0; i<OMXAV_H_NUMBER; i++) {
        if(0!=pOMXAV->comp[i]) {
            if(OMX_ErrorNone!=OMX_SendCommand(pOMXAV->comp[i], cmd, nParam1, pCmdData)) {
                return -1;
            }
            if(OMX_CommandFlush==cmd) {
                kdThreadSemWait(pOMXAV->flushSem);
            }
        }
    }
    return 0;
}

static KDint PlayStop(OMXToolBasicAV_t * pOMXAV);
static int DetachVideoRenderer(OMXToolBasicAV_t * pOMXAV);

static void DestroyInstanceUnlock(OMXToolBasicAV_t * pOMXAV)
{
    // 0: Stop
    // 1: X -> idle
    // 2: Disable all ports
    // 3: DetachVideoRenderer
    // 3: X -> loaded
    // 4: Free Handle
    // 5: Free mutex/semaphores/struct
    int i, res1=0, res2=0;
    if(NULL==pOMXAV) return;

    DBG_PRINT( "Destroy p1\n");
    PlayStop(pOMXAV);

    DBG_PRINT( "Destroy p2\n");
    if(0!=(res1=OMXToolBasicAV_RequestState(pOMXAV, OMX_StateIdle, KD_TRUE)))
    {
        JoglCommon_throwNewRuntimeException(0, "Destroy - Wait for Idle Failed (%d)", res1);
    }

    DBG_PRINT( "Destroy p3\n");
    SendCommand(pOMXAV, OMX_CommandPortDisable, OMX_ALL, 0); // Ignore error ..

    DBG_PRINT( "Destroy p3\n");
    DetachVideoRenderer(pOMXAV);

    DBG_PRINT( "Destroy p4\n");
    if(0!=(res2=OMXToolBasicAV_RequestState(pOMXAV, OMX_StateLoaded, KD_TRUE)))
    {
        if(!res1) {
            JoglCommon_throwNewRuntimeException(0, "Destroy - Wait for Loaded Failed (%d)", res2);
        }
    }

    DBG_PRINT( "Destroy p5\n");
    for(i=0; i<OMXAV_H_NUMBER; i++) {
        if(0!=pOMXAV->comp[i]) {
            OMX_FreeHandle(pOMXAV->comp[i]);
            pOMXAV->comp[i]=0;
        }
    }

    if(0!=pOMXAV->flushSem) {
        DBG_PRINT( "Destroy p6\n");
        kdThreadSemFree(pOMXAV->flushSem);
        pOMXAV->flushSem=0;
    }
    if(0!=pOMXAV->mutex) {
        DBG_PRINT( "Destroy p7\n");
        kdThreadMutexUnlock(pOMXAV->mutex);
        DBG_PRINT( "Destroy p8\n");
        kdThreadMutexFree(pOMXAV->mutex);
        pOMXAV->mutex=0;
    }

    DBG_PRINT( "Destroy DONE\n");

    free(pOMXAV);
}

static OMX_ERRORTYPE AddFile(OMXToolBasicAV_t * pOMXAV, const KDchar* filename)
{
// FIXME: Non NV case ..
#if 0 
    OMX_ERRORTYPE eError;
    NVX_PARAM_FILENAME oFilenameParam;
    OMX_INDEXTYPE eIndexParamFilename;

    eError = OMX_GetExtensionIndex(pOMXAV->comp[OMXAV_H_READER], NVX_INDEX_PARAM_FILENAME,
                               &eIndexParamFilename);
    if (eError != OMX_ErrorNone)
        return eError;

    INIT_PARAM(oFilenameParam);
    oFilenameParam.pFilename = (char*) filename;

    eError = OMX_SetParameter(pOMXAV->comp[OMXAV_H_READER], eIndexParamFilename, &oFilenameParam);
    if (eError != OMX_ErrorNone)
        return eError;

    return OMX_ErrorNone;
#else
    return OMX_ErrorNotImplemented;
#endif
}

static OMX_ERRORTYPE ProbePort(OMXToolBasicAV_t * pOMXAV, int port, KDchar *codec, KDchar* component)
{
// FIXME: Non NV case ..
#if 0 
    OMX_U32 roles = 1;
    OMX_ERRORTYPE err = OMX_ErrorNone;
    OMX_INDEXTYPE eParam;
    NVX_PARAM_STREAMTYPE oStreamType;
    OMX_PARAM_PORTDEFINITIONTYPE oPortDef;

    INIT_PARAM(oStreamType);
    INIT_PARAM(oPortDef);
    OMXSAFEERR(OMX_GetExtensionIndex(pOMXAV->comp[OMXAV_H_READER], NVX_INDEX_PARAM_STREAMTYPE, &eParam));

    oPortDef.nPortIndex = port;
    OMXSAFEERR(OMX_GetParameter(pOMXAV->comp[OMXAV_H_READER], OMX_IndexParamPortDefinition, &oPortDef));

    oStreamType.nPort = port;
    OMXSAFEERR(OMX_GetParameter(pOMXAV->comp[OMXAV_H_READER], eParam, &oStreamType));

    if (oPortDef.eDomain != OMX_PortDomainVideo &&
        oPortDef.eDomain != OMX_PortDomainAudio) {
        return OMX_ErrorNotImplemented;
    }

    switch (oStreamType.eStreamType)
    {
#define CODEC(a, b) case a: kdStrncat_s(codec, 128, b, kdStrlen(b)); break
        CODEC(NvxStreamType_MPEG4,  "mpeg4");
        CODEC(NvxStreamType_H264,   "avc");
        CODEC(NvxStreamType_H263,   "mpeg4");
        CODEC(NvxStreamType_WMV,    "vc1");
        CODEC(NvxStreamType_MP3,    "mp3");
        CODEC(NvxStreamType_AAC,    "aac");
        CODEC(NvxStreamType_AACSBR, "eaacplus");
        CODEC(NvxStreamType_BSAC,   "bsac");
        CODEC(NvxStreamType_WMA,    "wma");
        CODEC(NvxStreamType_WMAPro, "wmapro");
        CODEC(NvxStreamType_WMALossless, "wmalossless");
        CODEC(NvxStreamType_AMRWB,  "amrwb");
        CODEC(NvxStreamType_AMRNB,  "amrnb");
        CODEC(NvxStreamType_VORBIS, "vorbis");
#undef CODEC
        default:
            return OMX_ErrorNotImplemented;
    }

    {
        KDchar ocodec[256];
        OMX_U8 *tmp = (OMX_U8*) kdMalloc(OMX_MAX_STRINGNAME_SIZE + 1);
        kdMemset(tmp, 0, sizeof(OMX_U8) * (OMX_MAX_STRINGNAME_SIZE + 1));

        if (oPortDef.eDomain == OMX_PortDomainVideo)
            kdStrcpy_s(ocodec, 128, "video_decoder.");
        else if (oPortDef.eDomain == OMX_PortDomainAudio)
            kdStrcpy_s(ocodec, 128, "audio_decoder.");
        kdStrncat_s(ocodec, 128, codec, kdStrlen(codec));

        err = OMX_GetComponentsOfRole(codec, &roles, &tmp);
        kdStrcpy_s(component, 256, (KDchar*) tmp);
        kdFree(tmp);
        printf("%s(%s) -> %s\n", ocodec, codec, component);
    }

    return err != OMX_ErrorNone ? err : roles ? OMX_ErrorNone : OMX_ErrorComponentNotFound;
#else
    return OMX_ErrorNotImplemented;
#endif
}

static int StartClock(OMXToolBasicAV_t * pOMXAV, KDboolean start, KDfloat32 time) {
    OMX_TIME_CONFIG_CLOCKSTATETYPE oClockState;
    OMX_ERRORTYPE eError = OMX_ErrorNone;
    int loop=STATE_TIMEOUT_LOOP;
    INIT_PARAM(oClockState);
    oClockState.nOffset = 0;
    oClockState.nStartTime = (KD_TRUE==start)? (OMX_TICKS) (time * 1000.0 * 1000.0) : 0;
    oClockState.nWaitMask = 0;
    oClockState.eState = (KD_TRUE==start)?OMX_TIME_ClockStateRunning:OMX_TIME_ClockStateStopped;

    eError = OMX_SetConfig(pOMXAV->comp[OMXAV_H_CLOCK], OMX_IndexConfigTimeClockState, &oClockState);
    while (loop>0 && OMX_ErrorNotReady == eError)
    {
        DBG_PRINT( "Play 3.2\n");
        usleep(STATE_SLEEP*1000);
        loop--;
        eError = OMX_SetConfig(pOMXAV->comp[OMXAV_H_CLOCK], OMX_IndexConfigTimeClockState,
                               &oClockState);
    }
    return (OMX_ErrorNotReady == eError)?-1:0;
}

static KDint64 GetClockPosition(OMXToolBasicAV_t * pOMXAV)
{
    OMX_TIME_CONFIG_TIMESTAMPTYPE stamp;
    INIT_PARAM(stamp);
    stamp.nPortIndex = 0;

    OMX_GetConfig(pOMXAV->comp[OMXAV_H_CLOCK], OMX_IndexConfigTimeCurrentMediaTime, &stamp);
    return (KDint64) ( stamp.nTimestamp / 1000L );
}

static KDfloat32 GetClockScale(OMXToolBasicAV_t * pOMXAV)
{
    OMX_TIME_CONFIG_SCALETYPE pScale;
    INIT_PARAM(pScale);

    OMX_GetConfig(pOMXAV->comp[OMXAV_H_CLOCK], OMX_IndexConfigTimeScale, &pScale);
    return (pScale.xScale / 65536.0f);
}

static KDint SetClockScale(OMXToolBasicAV_t * pOMXAV, KDfloat32 scale)
{
    OMX_TIME_CONFIG_SCALETYPE pScale;
    INIT_PARAM(pScale);
    pScale.xScale = (int) (scale * (1<<16));

    OMX_SetConfig(pOMXAV->comp[OMXAV_H_CLOCK], OMX_IndexConfigTimeScale, &pScale);
    return 0;
}

static int SetMediaPosition(OMXToolBasicAV_t * pOMXAV, KDint64 time) {
    OMX_ERRORTYPE eError = OMX_ErrorNone;
    OMX_TIME_CONFIG_TIMESTAMPTYPE timestamp;
    int loop=STATE_TIMEOUT_LOOP;
    INIT_PARAM(timestamp);
    timestamp.nPortIndex = 0;
    timestamp.nTimestamp = (OMX_TICKS) (time * 1000L);

    eError = OMX_SetConfig(pOMXAV->comp[OMXAV_H_READER], OMX_IndexConfigTimePosition, &timestamp);
    while (loop>0 && OMX_ErrorNotReady == eError)
    {
        usleep(STATE_SLEEP*1000);
        loop--;
        eError = OMX_SetConfig(pOMXAV->comp[OMXAV_H_READER], OMX_IndexConfigTimePosition, &timestamp);
    }
    return (OMX_ErrorNotReady == eError)?-1:0;
}

static KDfloat32 GetMediaLength(OMXToolBasicAV_t * pOMXAV)
{
// FIXME: Non NV case ..
#if 0 
    NVX_PARAM_DURATION oDuration;
    OMX_INDEXTYPE eParam;

    if (OMX_ErrorNone != OMX_GetExtensionIndex(pOMXAV->comp[OMXAV_H_READER], NVX_INDEX_PARAM_DURATION, &eParam))
        return -1.0f;

    if (OMX_ErrorNone != OMX_GetParameter(pOMXAV->comp[OMXAV_H_READER], eParam, &oDuration))
        return -1.0f;

    return (KDfloat32) (oDuration.nDuration * (1.0f/(1000.0f*1000.0f)));
#else
    return -1.0f;
#endif
}

static OMX_ERRORTYPE UpdateStreamInfo(OMXToolBasicAV_t * pOMXAV, KDboolean issueCallback)
{
    OMX_ERRORTYPE err = OMX_ErrorNone;
    OMX_PARAM_PORTDEFINITIONTYPE oPortDef;

    DBG_PRINT( "Update StreamInfo p0\n" );

    kdMemset(&oPortDef, 0, sizeof(oPortDef));
    oPortDef.nSize = sizeof(oPortDef);
    oPortDef.nVersion.s.nVersionMajor = 1;
    oPortDef.nVersion.s.nVersionMinor = 1;
    oPortDef.nPortIndex = 0;
    err = OMX_GetParameter(pOMXAV->comp[OMXAV_H_READER], OMX_IndexParamPortDefinition, &oPortDef);
    if(OMX_ErrorNone!=err) {
        fprintf(stderr, "UpdateStreamInfo failed - p1 0x%X", err);
        return err;
    }

    if (oPortDef.eDomain != OMX_PortDomainVideo)
    {
        kdMemset(&oPortDef, 0, sizeof(oPortDef));
        oPortDef.nSize = sizeof(oPortDef);
        oPortDef.nVersion.s.nVersionMajor = 1;
        oPortDef.nVersion.s.nVersionMinor = 1;
        oPortDef.nPortIndex = 1;
        err = OMX_GetParameter(pOMXAV->comp[OMXAV_H_READER], OMX_IndexParamPortDefinition, &oPortDef);
        if(OMX_ErrorNone!=err) {
            fprintf(stderr, "UpdateStreamInfo failed - p2 0x%X", err);
            return err;
        }
    }

    DBG_PRINT( "Update StreamInfo p1\n" );

    pOMXAV->width = oPortDef.format.video.nFrameWidth;
    pOMXAV->height = oPortDef.format.video.nFrameHeight;
    /* pOMXAV->stride = oPortDef.format.video.nStride;
    pOMXAV->sliceHeight = oPortDef.format.video.nSliceHeight; */
    pOMXAV->framerate = (float) oPortDef.format.video.xFramerate;
    pOMXAV->bitrate = oPortDef.format.video.nBitrate;
    DBG_PRINT( "Update StreamInfo p2 %dx%d, fps %f, bps %d\n", pOMXAV->width, pOMXAV->height, pOMXAV->framerate, pOMXAV->bitrate );
    pOMXAV->length = GetMediaLength(pOMXAV);
    pOMXAV->speed = GetClockScale(pOMXAV);

    if(issueCallback) {
        OMXInstance_UpdateJavaAttributes(pOMXAV);
    }

    return err;
}

static int AttachAudioRenderer(OMXToolBasicAV_t * pOMXAV)
{
// FIXME: Non NV case ..
#if 0 
    int res=0;
    // Configure audio port

    if (USE_AUDIOBUFFERING) 
    {
        // FIXME: proper audio buffering .. 
        OMXSAFE(OMX_GetHandle(&pOMXAV->comp[OMXAV_H_ABUFFERING], "OMX.Nvidia.audio.visualization", pOMXAV, &pOMXAV->callbacks));
        if(0!=(res=SyncOnState(pOMXAV->comp[OMXAV_H_ABUFFERING], OMX_StateLoaded))) {
            JoglCommon_throwNewRuntimeException(0, "Loading AudioBuffering Failed (%d)", res);
            return res;
        }
        /**
        if (m_settings.m_avsync)
        {
            // Tweak the avsync parameter
            NVX_CONFIG_VISU conf;
            INIT_PARAM(conf);
            conf.nAVSyncOffset = m_settings.m_avsync;

            OMX_INDEXTYPE idx;
            OMX_GetExtensionIndex(pOMXAV->comp[OMXAV_H_ABUFFERING], NVX_INDEX_PARAM_VISUDATA, &idx);
            OMX_SetConfig(pOMXAV->comp[OMXAV_H_ABUFFERING], idx, &conf);
        }*/
    }

    OMXSAFE(OMX_GetHandle(&pOMXAV->comp[OMXAV_H_ARENDERER], "OMX.Nvidia.audio.render",pOMXAV, &pOMXAV->callbacks));
    pOMXAV->endComponent = pOMXAV->comp[OMXAV_H_ARENDERER];

    // mandatory before SetupTunnel
    if(0!=(res=SyncOnState(pOMXAV->comp[OMXAV_H_ARENDERER], OMX_StateLoaded))) {
        JoglCommon_throwNewRuntimeException(0, "Loading AudioRenderer Failed (%d)", res);
        return res;
    }

    {
        OMX_INDEXTYPE eIndexConfigOutputType;
        NVX_CONFIG_AUDIOOUTPUT ao;
        OMX_ERRORTYPE eError;

        INIT_PARAM(ao);

        eError = OMX_GetExtensionIndex(pOMXAV->comp[OMXAV_H_ARENDERER], NVX_INDEX_CONFIG_AUDIO_OUTPUT,
                                       &eIndexConfigOutputType);
        if (eError != OMX_ErrorNoMore)
        {   
            /** FIXME: HDMI configuration ..
             // for now, only put audio out hdmi if the settings say to, regardless of the hdmi-video flag.
             // if (// m_settings.m_hdmiVideo || // m_settings.m_hdmiAudio)
             //    ao.eOutputType = NVX_AUDIO_OutputHdmi;
            else */
                ao.eOutputType = NVX_AUDIO_OutputI2S;

            OMX_SetConfig(pOMXAV->comp[OMXAV_H_ARENDERER], eIndexConfigOutputType, &ao);
        }
    }

    OMXSAFE(OMX_SendCommand(pOMXAV->comp[OMXAV_H_CLOCK],     OMX_CommandPortEnable,   pOMXAV->audioPort, 0));
    OMXSAFE(OMX_SendCommand(pOMXAV->comp[OMXAV_H_ARENDERER], OMX_CommandPortEnable, 1, 0));

    if (USE_AUDIOBUFFERING) 
    {
        OMXSAFE(OMX_SetupTunnel(pOMXAV->comp[OMXAV_H_ADECODER], 1, pOMXAV->comp[OMXAV_H_ABUFFERING], 0));
        OMXSAFE(OMX_SetupTunnel(pOMXAV->comp[OMXAV_H_ABUFFERING], 1, pOMXAV->comp[OMXAV_H_ARENDERER], 0));
    }
    else
    {
        OMXSAFE(OMX_SetupTunnel(pOMXAV->comp[OMXAV_H_ADECODER], 1, pOMXAV->comp[OMXAV_H_ARENDERER], 0));
    }

    OMXSAFE(OMX_SetupTunnel(pOMXAV->comp[OMXAV_H_CLOCK],  pOMXAV->audioPort, pOMXAV->comp[OMXAV_H_ARENDERER], 1));

    return OMX_ErrorNone;
#else
    return OMX_ErrorNotImplemented;
#endif
}

static int AttachVideoRenderer(OMXToolBasicAV_t * pOMXAV)
{
    int i, res=0;
    if(KD_NULL!=pOMXAV->comp[OMXAV_H_VSCHEDULER]) {
        JoglCommon_throwNewRuntimeException(0, "Detach Video first");
        return -1;
    }
    OMXSAFE(OMX_GetHandle(&pOMXAV->comp[OMXAV_H_VSCHEDULER], "OMX.Nvidia.video.scheduler", pOMXAV, &pOMXAV->callbacks));
    pOMXAV->endComponent = pOMXAV->comp[OMXAV_H_VSCHEDULER];

    // mandatory before SetupTunnel
    if(0!=(res=SyncOnState(pOMXAV->comp[OMXAV_H_VSCHEDULER], OMX_StateLoaded))) {
        JoglCommon_throwNewRuntimeException(0, "Loading VideoScheduler Failed (%d)", res);
        return res;
    }
    // mandatory before EGLUseImage
    OMXSAFE(RequestState(pOMXAV->comp[OMXAV_H_VSCHEDULER], OMX_StateIdle, KD_FALSE));

    DBG_PRINT( "Attach VR %p c:%p\n", pOMXAV, pOMXAV->comp[OMXAV_H_VSCHEDULER]);
    OMXSAFE(UpdateStreamInfo(pOMXAV, KD_FALSE));

    DBG_PRINT( "UseEGLImg port enable/tunneling %p\n", pOMXAV);
    OMXSAFE(OMX_SendCommand(pOMXAV->comp[OMXAV_H_CLOCK],      OMX_CommandPortEnable, PORT_VRENDERER, 0));
    OMXSAFE(OMX_SendCommand(pOMXAV->comp[OMXAV_H_VDECODER],   OMX_CommandPortEnable,              1, 0));
    OMXSAFE(OMX_SendCommand(pOMXAV->comp[OMXAV_H_VSCHEDULER], OMX_CommandPortEnable,              0, 0));
    OMXSAFE(OMX_SendCommand(pOMXAV->comp[OMXAV_H_VSCHEDULER], OMX_CommandPortEnable,              2, 0));
    OMXSAFE(OMX_SetupTunnel(pOMXAV->comp[OMXAV_H_VDECODER],            1, pOMXAV->comp[OMXAV_H_VSCHEDULER], 0));
    OMXSAFE(OMX_SetupTunnel(pOMXAV->comp[OMXAV_H_CLOCK],  PORT_VRENDERER, pOMXAV->comp[OMXAV_H_VSCHEDULER], 2));

    for (i = 0; i < pOMXAV->vBufferNum; i++) {
        OMXToolImageBuffer_t *pBuf = &pOMXAV->buffers[i];
        // The Texture, EGLImage and EGLSync was created by the Java client,
        // and registered using the OMXToolBasicAV_SetStreamEGLImageTexture2D command.

        DBG_PRINT( "UseEGLImg %p #%d t:%d i:%p s:%p p1\n", pOMXAV, i, pBuf->tex, pBuf->image, pBuf->sync);

        if(NULL==pBuf->image) {
            JoglCommon_throwNewRuntimeException(0, "AttachVideoRenderer: User didn't set buffer %d/%d\n", i, pOMXAV->vBufferNum);
            return -1;
        } else  {
            // tell decoder output port that it will be using EGLImage
            OMXSAFE(OMX_UseEGLImage(
                    pOMXAV->comp[OMXAV_H_VSCHEDULER],
                    &pBuf->omxBufferHeader,
                    1,      // The port to use the EGLImage for
                    pOMXAV, // app private data
                    pBuf->image));
        }
        DBG_PRINT( "UseEGLImg %p #%d t:%d i:%p s:%p b:%p - p2\n", 
            pOMXAV, i, pBuf->tex, pBuf->image, pBuf->sync, pBuf->omxBufferHeader);
    }

    DBG_PRINT( "UseEGLImg %p #%d DONE\n", pOMXAV, i);
    return 0;
}

static int DetachVideoRenderer(OMXToolBasicAV_t * pOMXAV)
{
    int i;
    if(NULL==pOMXAV) return -1;

    if(KD_NULL==pOMXAV->comp[OMXAV_H_VSCHEDULER]) {
        JoglCommon_throwNewRuntimeException(0, "Attach Video first");
        return -1;
    }
    DBG_PRINT( "DetachVideoRenderer p0\n");
    if(0==CheckState(pOMXAV->comp[OMXAV_H_VSCHEDULER], OMX_StateLoaded)) {
        DBG_PRINT( "DetachVideoRenderer DONE (already state loaded)\n");
        return 0;
    }
    OMXSAFE(RequestState(pOMXAV->comp[OMXAV_H_VSCHEDULER], OMX_StateIdle, KD_TRUE));

    DBG_PRINT( "DetachVideoRenderer p1\n");
    OMXSAFE(OMX_SendCommand(pOMXAV->comp[OMXAV_H_CLOCK],      OMX_CommandPortDisable, PORT_VRENDERER, 0));
    OMXSAFE(OMX_SendCommand(pOMXAV->comp[OMXAV_H_VDECODER],   OMX_CommandPortDisable,              1, 0));
    OMXSAFE(OMX_SendCommand(pOMXAV->comp[OMXAV_H_VSCHEDULER], OMX_CommandPortDisable,              0, 0));
    OMXSAFE(OMX_SendCommand(pOMXAV->comp[OMXAV_H_VSCHEDULER], OMX_CommandPortDisable,              2, 0));
    DBG_PRINT( "DetachVideoRenderer p2\n");

    for (i = 0; i < pOMXAV->vBufferNum; i++) {
        OMXToolImageBuffer_t *pBuf = &pOMXAV->buffers[i];

        // tell decoder output port to stop using EGLImage
        if (NULL!=pBuf->omxBufferHeader) {
            OMX_FreeBuffer(
                    pOMXAV->comp[OMXAV_H_VSCHEDULER],
                    1,
                    pBuf->omxBufferHeader);
            pBuf->omxBufferHeader=NULL;
        }
    }

    OMXSAFE(RequestState(pOMXAV->comp[OMXAV_H_VSCHEDULER], OMX_StateLoaded, KD_TRUE));
    DBG_PRINT( "DetachVideoRenderer p3\n");

    OMX_FreeHandle(pOMXAV->comp[OMXAV_H_VSCHEDULER]);
    pOMXAV->comp[OMXAV_H_VSCHEDULER]=NULL;
    DBG_PRINT( "DetachVideoRenderer DONE\n");
    return 0;
}

OMXToolBasicAV_t * OMXToolBasicAV_CreateInstance(EGLDisplay dpy)
{
    int i;
    OMXToolBasicAV_t * pOMXAV = NULL;
    InitStatic();

    pOMXAV = malloc(sizeof(OMXToolBasicAV_t));
    if(NULL==pOMXAV) {
        DBG_PRINT( "Init struct failed!\n");
        return NULL;
    }
    memset(pOMXAV, 0, sizeof(OMXToolBasicAV_t));

    pOMXAV->dpy = dpy;

    pOMXAV->audioPort=-1;
    pOMXAV->videoPort=-1;

    for(i=0; i<OMXAV_H_NUMBER; i++) {
        pOMXAV->comp[i] = KD_NULL;
    }

    pOMXAV->callbacks.EventHandler    = EventHandler;
    pOMXAV->callbacks.EmptyBufferDone = EmptyBufferDone;
    pOMXAV->callbacks.FillBufferDone  = FillBufferDone;

    pOMXAV->mutex = kdThreadMutexCreate(KD_NULL);
    pOMXAV->flushSem = kdThreadSemCreate(0);

    pOMXAV->play_speed = 1.0f;
    pOMXAV->status=OMXAV_INIT;

    return pOMXAV;
}

void OMXToolBasicAV_SetStream(OMXToolBasicAV_t * pOMXAV, int vBufferNum, const KDchar * stream)
{
    OMX_ERRORTYPE eError = OMX_ErrorNone;

    DBG_PRINT( "SetStream 1 %s  ..\n", stream);

    // FIXME: verify player state .. ie stop !
    if(pOMXAV->status!=OMXAV_INIT) {
        JoglCommon_throwNewRuntimeException(0, "Player instance in use\n");
        return;
    }
    if(vBufferNum>EGLIMAGE_MAX_BUFFERS) {
        JoglCommon_throwNewRuntimeException(0, "buffer number %d > MAX(%d)\n", vBufferNum, EGLIMAGE_MAX_BUFFERS);
        return;
    }

    kdThreadMutexLock(pOMXAV->mutex);

    DBG_PRINT( "SetStream 3\n");

    pOMXAV->vBufferNum = vBufferNum;

    // Use the "super parser" :) FIXME: Non NV case ..
    eError = OMX_GetHandle(&pOMXAV->comp[OMXAV_H_READER], "OMX.Nvidia.reader", pOMXAV, &pOMXAV->callbacks);

    eError = AddFile(pOMXAV, stream);
    if(eError!=OMX_ErrorNone) {
        JoglCommon_throwNewRuntimeException(0, "Couldn't open or handle stream: %s\n", stream);
        kdThreadMutexUnlock(pOMXAV->mutex);
        return;
    }

    DBG_PRINT( "SetStream 4\n");

    // Auto detect codecs
    {
        OMX_PARAM_PORTDEFINITIONTYPE oPortDef;
        INIT_PARAM(oPortDef);
        oPortDef.nPortIndex = 0;
        pOMXAV->videoPort = -1;
        pOMXAV->audioPort = -1;
        OMXSAFEVOID(OMX_GetParameter(pOMXAV->comp[OMXAV_H_READER], OMX_IndexParamPortDefinition, &oPortDef));

        if (oPortDef.eDomain == OMX_PortDomainAudio)
            pOMXAV->audioPort = oPortDef.nPortIndex;
        else if (oPortDef.eDomain == OMX_PortDomainVideo)
            pOMXAV->videoPort = oPortDef.nPortIndex;
        else
            OMXSAFEVOID(OMX_ErrorNotImplemented);

        INIT_PARAM(oPortDef);
        oPortDef.nPortIndex = 1;
        if (OMX_GetParameter(pOMXAV->comp[OMXAV_H_READER], OMX_IndexParamPortDefinition, &oPortDef) == OMX_ErrorNone)
        {
            if (oPortDef.eDomain == OMX_PortDomainAudio)
                pOMXAV->audioPort = oPortDef.nPortIndex;
            else if (oPortDef.eDomain == OMX_PortDomainVideo)
                pOMXAV->videoPort = oPortDef.nPortIndex;
            else
                OMXSAFEVOID(OMX_ErrorNotImplemented);
        }
        if (pOMXAV->audioPort != -1)
        {
            if (ProbePort(pOMXAV, pOMXAV->audioPort, pOMXAV->audioCodec, pOMXAV->audioCodecComponent) != OMX_ErrorNone)
            {
                printf("disabling audio port\n");
                OMXSAFEVOID(OMX_SendCommand(pOMXAV->comp[OMXAV_H_READER], OMX_CommandPortDisable, pOMXAV->audioPort, 0));
                pOMXAV->audioPort = -1;
            }
        }
        if (pOMXAV->videoPort != -1)
            if (ProbePort(pOMXAV, pOMXAV->videoPort, pOMXAV->videoCodec, pOMXAV->videoCodecComponent) != OMX_ErrorNone)
            {
                printf("disabling video port\n");
                OMXSAFEVOID(OMX_SendCommand(pOMXAV->comp[OMXAV_H_READER], OMX_CommandPortDisable, pOMXAV->videoPort, 0));
                pOMXAV->videoPort = -1;
            }

        if (pOMXAV->audioPort == -1 && pOMXAV->videoPort == -1)
        {
            JoglCommon_throwNewRuntimeException(0, "Neither audioport or videoport could be played back!\n");
            kdThreadMutexUnlock(pOMXAV->mutex);
            return;
        }
    }
    DBG_PRINT( "SetStream 5 ; audioPort %d, videoPort %d\n", pOMXAV->audioPort, pOMXAV->videoPort);

    OMXSAFEVOID(OMX_GetHandle(&pOMXAV->comp[OMXAV_H_CLOCK],     "OMX.Nvidia.clock.component", pOMXAV, &pOMXAV->callbacks));

    DBG_PRINT( "Configuring comp[OMXAV_H_CLOCK]\n");
    {

        OMX_TIME_CONFIG_ACTIVEREFCLOCKTYPE oActiveClockType;
        INIT_PARAM(oActiveClockType);
        oActiveClockType.eClock = (pOMXAV->audioPort != -1) ?
                    OMX_TIME_RefClockAudio : OMX_TIME_RefClockVideo;
        OMXSAFEVOID(OMX_SetConfig(pOMXAV->comp[OMXAV_H_CLOCK], OMX_IndexConfigTimeActiveRefClock,
                               &oActiveClockType));
    }
    OMXSAFEVOID(OMX_SendCommand(pOMXAV->comp[OMXAV_H_CLOCK], OMX_CommandPortDisable, (OMX_U32) -1, 0));

    OMXSAFEVOID(UpdateStreamInfo(pOMXAV, KD_FALSE));

    kdThreadMutexUnlock(pOMXAV->mutex);

    DBG_PRINT( "SetStream X\n");
}

void OMXToolBasicAV_SetStreamEGLImageTexture2D(OMXToolBasicAV_t * pOMXAV, KDint i, GLuint tex, EGLImageKHR image, EGLSyncKHR sync)
{
    if(NULL==pOMXAV) {
        JoglCommon_throwNewRuntimeException(0, "OMX instance null\n");
        return;
    }
    DBG_PRINT( "SetStreamEGLImg %p #%d/%d t:%d i:%p s:%p..\n", pOMXAV, i, pOMXAV->vBufferNum, tex, image, sync);
    if(i<0||i>=pOMXAV->vBufferNum) {
        JoglCommon_throwNewRuntimeException(0, "Buffer index out of range: %d\n", i);
        return;
    }

    kdThreadMutexLock(pOMXAV->mutex);
    {
        OMXToolImageBuffer_t *pBuf = &pOMXAV->buffers[i];
        pBuf->tex=tex;
        pBuf->image=image;
        pBuf->sync=sync;

    }
    kdThreadMutexUnlock(pOMXAV->mutex);
}

void OMXToolBasicAV_ActivateStream(OMXToolBasicAV_t * pOMXAV) {
    int res;
    if(NULL==pOMXAV) {
        JoglCommon_throwNewRuntimeException(0, "OMX instance null\n");
        return;
    }
    DBG_PRINT( "ActivateStream 1\n");

    kdThreadMutexLock(pOMXAV->mutex);

    if (pOMXAV->audioPort != -1)
    {
        OMXSAFEVOID(OMX_GetHandle(&pOMXAV->comp[OMXAV_H_ADECODER],  pOMXAV->audioCodecComponent, pOMXAV, &pOMXAV->callbacks));
    }

    if (pOMXAV->videoPort != -1)
    {
        OMXSAFEVOID(OMX_GetHandle(&pOMXAV->comp[OMXAV_H_VDECODER],  pOMXAV->videoCodecComponent, pOMXAV, &pOMXAV->callbacks));
    }

    //
    // mandatory: before SetupTunnel (->Activate), wait until all devices are ready ..
    //            arender/vrender must wait as well ..
    if(0!=(res=OMXToolBasicAV_WaitForState(pOMXAV, OMX_StateLoaded))) {
        JoglCommon_throwNewRuntimeException(0, "Loaded Failed (%d)", res);
        kdThreadMutexUnlock(pOMXAV->mutex);
        return;
    }

    if (pOMXAV->audioPort != -1)
    {
        if(0!=(res=AttachAudioRenderer(pOMXAV))) {
            kdThreadMutexUnlock(pOMXAV->mutex);
            return; // exception thrown
        }
    }

    if (pOMXAV->videoPort != -1)
    {
        if(0!=(res=AttachVideoRenderer(pOMXAV))) {
            kdThreadMutexUnlock(pOMXAV->mutex);
            return; // exception thrown
        }
    }

    DBG_PRINT( "Setup tunneling\n");
    {
        // do tunneling
        if (pOMXAV->audioPort != -1)
        {
            DBG_PRINT( "Setup tunneling audio\n");
            OMXSAFEVOID(OMX_SetupTunnel(pOMXAV->comp[OMXAV_H_READER], pOMXAV->audioPort, pOMXAV->comp[OMXAV_H_ADECODER],  0));
            // The rest of the audio port is configured in AttachAudioRenderer
        }
        
        if (pOMXAV->videoPort != -1)
        {
            DBG_PRINT( "Setup tunneling video\n");
            OMXSAFEVOID(OMX_SetupTunnel(pOMXAV->comp[OMXAV_H_READER], pOMXAV->videoPort, pOMXAV->comp[OMXAV_H_VDECODER],  0));
            // The rest of the video port is configured in AttachVideoRenderer
        }
    }
    DBG_PRINT( "ActivateStream .. %p\n", pOMXAV);

    //
    // mandatory: wait until all devices are idle
    //            failure means not all necessary ports/buffer are set.
    //
    if(0!=(res=OMXToolBasicAV_RequestState(pOMXAV, OMX_StateIdle, KD_TRUE)))
    {
        JoglCommon_throwNewRuntimeException(0, "Wait for Idle Failed (%d)", res);
        kdThreadMutexUnlock(pOMXAV->mutex);
        return;
    }
    pOMXAV->status=OMXAV_STOPPED;
    kdThreadMutexUnlock(pOMXAV->mutex);
    DBG_PRINT( "ActivateStream done %p\n", pOMXAV);
}

void OMXToolBasicAV_DetachVideoRenderer(OMXToolBasicAV_t * pOMXAV) {
    if(NULL==pOMXAV) {
        JoglCommon_throwNewRuntimeException(0, "OMX instance null\n");
        return;
    }
    if(pOMXAV->status<=OMXAV_INIT) {
        JoglCommon_throwNewRuntimeException(0, "OMX invalid status: %d <= INIT\n", pOMXAV->status);
        return;
    }
    kdThreadMutexLock(pOMXAV->mutex);

    (void) DetachVideoRenderer(pOMXAV);

    kdThreadMutexUnlock(pOMXAV->mutex);
}

void OMXToolBasicAV_AttachVideoRenderer(OMXToolBasicAV_t * pOMXAV) {
    if(NULL==pOMXAV) {
        JoglCommon_throwNewRuntimeException(0, "OMX instance null\n");
        return;
    }
    if(pOMXAV->status<=OMXAV_INIT) {
        JoglCommon_throwNewRuntimeException(0, "OMX invalid status: %d <= INIT\n", pOMXAV->status);
        return;
    }
    kdThreadMutexLock(pOMXAV->mutex);

    (void) AttachVideoRenderer(pOMXAV);

    kdThreadMutexUnlock(pOMXAV->mutex);
}

void OMXToolBasicAV_SetPlaySpeed(OMXToolBasicAV_t * pOMXAV, KDfloat32 scale)
{
    if(NULL==pOMXAV) {
        JoglCommon_throwNewRuntimeException(0, "OMX instance null\n");
        return;
    }
    if(pOMXAV->status<=OMXAV_INIT) {
        JoglCommon_throwNewRuntimeException(0, "OMX invalid status: %d <= INIT\n", pOMXAV->status);
        return;
    }
    kdThreadMutexLock(pOMXAV->mutex);

    if(!SetClockScale(pOMXAV, scale)) {
        pOMXAV->play_speed=scale;
    }

    kdThreadMutexUnlock(pOMXAV->mutex);
}


void OMXToolBasicAV_PlayStart(OMXToolBasicAV_t * pOMXAV)
{
    int res;
    if(NULL==pOMXAV) {
        JoglCommon_throwNewRuntimeException(0, "OMX instance null\n");
        return;
    }
    if(pOMXAV->status<=OMXAV_INIT) {
        JoglCommon_throwNewRuntimeException(0, "OMX invalid status: %d <= INIT\n", pOMXAV->status);
        return;
    }
    if(pOMXAV->status==OMXAV_PLAYING) {
        return;
    }

    kdThreadMutexLock(pOMXAV->mutex);
    DBG_PRINT( "Play 2\n");

    SetClockScale(pOMXAV, pOMXAV->play_speed);

    DBG_PRINT( "Play 3.1\n");
    if(0!=(res=OMXToolBasicAV_RequestState(pOMXAV, OMX_StateExecuting, KD_TRUE))) {
        JoglCommon_throwNewRuntimeException(0, "Play Execute Failed (%d)", res);
        kdThreadMutexUnlock(pOMXAV->mutex);
        return;
    }
    if(pOMXAV->status==OMXAV_STOPPED || pOMXAV->status==OMXAV_FIN) {
        DBG_PRINT( "Play 3.2\n");
        if(StartClock(pOMXAV, KD_TRUE, 0.0)) {
            JoglCommon_throwNewRuntimeException(0, "Play StartClock Failed");
            kdThreadMutexUnlock(pOMXAV->mutex);
            return;
        }
    }
    DBG_PRINT( "Play 4.0\n");

    kdThreadMutexUnlock(pOMXAV->mutex);
    pOMXAV->status=OMXAV_PLAYING;
    DBG_PRINT( "Play DONE\n");
}

static int PlayStop(OMXToolBasicAV_t * pOMXAV)
{
    int res;
    if(NULL==pOMXAV || pOMXAV->status<=OMXAV_INIT) {
        return -1;
    }
    if( pOMXAV->status!=OMXAV_PLAYING && pOMXAV->status!=OMXAV_PAUSED ) {
        return -1;
    }

    if(OMXToolBasicAV_CheckState(pOMXAV, OMX_StateLoaded)) {
        if(StartClock(pOMXAV, KD_FALSE, 0.0)) {
            JoglCommon_throwNewRuntimeException(0, "Stop StopClock Failed");
            kdThreadMutexUnlock(pOMXAV->mutex);
            return -1;
        }
        if(OMXToolBasicAV_CheckState(pOMXAV, OMX_StateIdle)) {
            if(0!=(res=OMXToolBasicAV_RequestState(pOMXAV, OMX_StateIdle, KD_TRUE))) {
                JoglCommon_throwNewRuntimeException(0, "Stop Idle Failed (%d)", res);
                kdThreadMutexUnlock(pOMXAV->mutex);
                return res;
            }
        }
    }
    pOMXAV->status=OMXAV_STOPPED;
    return 0;
}

void OMXToolBasicAV_PlayStop(OMXToolBasicAV_t * pOMXAV)
{
    if(NULL==pOMXAV) {
        JoglCommon_throwNewRuntimeException(0, "OMX instance null\n");
        return;
    }
    if(pOMXAV->status<=OMXAV_INIT) {
        JoglCommon_throwNewRuntimeException(0, "OMX invalid status: %d <= INIT\n", pOMXAV->status);
        return;
    }
    kdThreadMutexLock(pOMXAV->mutex);

    (void) PlayStop(pOMXAV);

    kdThreadMutexUnlock(pOMXAV->mutex);
}

void OMXToolBasicAV_PlayPause(OMXToolBasicAV_t * pOMXAV)
{
    int res;
    if(NULL==pOMXAV) {
        JoglCommon_throwNewRuntimeException(0, "OMX instance null\n");
        return;
    }
    if(pOMXAV->status<=OMXAV_INIT) {
        JoglCommon_throwNewRuntimeException(0, "OMX invalid status: %d <= INIT\n", pOMXAV->status);
        return;
    }
    if(pOMXAV->status==OMXAV_PAUSED || pOMXAV->status!=OMXAV_PLAYING) {
        return;
    }

    kdThreadMutexLock(pOMXAV->mutex);
    SetClockScale(pOMXAV, 0);
    if(0!=(res=OMXToolBasicAV_RequestState(pOMXAV, OMX_StatePause, KD_TRUE))) {
        fprintf(stderr, "Err: Pause Pause Failed (%d)", res);
        kdThreadMutexUnlock(pOMXAV->mutex);
        return;
    }
    pOMXAV->status=OMXAV_PAUSED;
    kdThreadMutexUnlock(pOMXAV->mutex);
}

void OMXToolBasicAV_PlaySeek(OMXToolBasicAV_t * pOMXAV, KDint64 time)
{
    int res;

    if(NULL==pOMXAV) {
        JoglCommon_throwNewRuntimeException(0, "OMX instance null\n");
        return;
    }
    if(pOMXAV->status<=OMXAV_INIT) {
        JoglCommon_throwNewRuntimeException(0, "OMX invalid status: %d <= INIT\n", pOMXAV->status);
        return;
    }
    kdThreadMutexLock(pOMXAV->mutex);

    pOMXAV->length = GetMediaLength(pOMXAV);
    if(pOMXAV->length<=time) {
        (void) PlayStop(pOMXAV);
        kdThreadMutexUnlock(pOMXAV->mutex);
        return;
    }

    // 1. Pause the component through the use of OMX_SendCommand requesting a
    //    state transition to OMX_StatePause.
    if(pOMXAV->status!=OMXAV_PAUSED) {
        if(0!=(res=OMXToolBasicAV_RequestState(pOMXAV, OMX_StatePause, KD_TRUE))) {
            JoglCommon_throwNewRuntimeException(0, "Seek Pause Failed (%d)", res);
            kdThreadMutexUnlock(pOMXAV->mutex);
            return;
        }
    }

    // 2. Stop the comp[OMXAV_H_CLOCK] component’s media comp[OMXAV_H_CLOCK] through the use of OMX_SetConfig
    //    on OMX_TIME_CONFIG_CLOCKSTATETYPE requesting a transition to
    //    OMX_TIME_ClockStateStopped.
    if(StartClock(pOMXAV, KD_FALSE, 0.0)) {
        JoglCommon_throwNewRuntimeException(0, "Seek StopClock Failed");
        kdThreadMutexUnlock(pOMXAV->mutex);
        return;
    }

    // 3. Seek to the desired location through the use of OMX_SetConfig on
    //    OMX_IndexConfigTimePosition requesting the desired timestamp.
    if(SetMediaPosition(pOMXAV, time)) {
        JoglCommon_throwNewRuntimeException(0, "Seek position Failed");
        kdThreadMutexUnlock(pOMXAV->mutex);
        return;
    }

    // 4. Flush all components.
    if(SendCommand(pOMXAV, OMX_CommandFlush, OMX_ALL, 0)) {
        JoglCommon_throwNewRuntimeException(0, "Seek Flush Failed");
        kdThreadMutexUnlock(pOMXAV->mutex);
        return;
    }

    // 5. Start the comp[OMXAV_H_CLOCK] component’s media comp[OMXAV_H_CLOCK] through the use of OMX_SetConfig
    //    on OMX_TIME_CONFIG_CLOCKSTATETYPE requesting a transition to either
    //    OMX_TIME_ClockStateRunning or
    //    OMX_TIME_ClockStateWaitingForStartTime.
    if(StartClock(pOMXAV, KD_TRUE, time)) {
        JoglCommon_throwNewRuntimeException(0, "Seek StartClock Failed");
        kdThreadMutexUnlock(pOMXAV->mutex);
        return;
    }

    // 6. Un-pause the component through the use of OMX_SendCommand requesting a
    //    state transition to OMX_StateExecuting.
    if(pOMXAV->status==OMXAV_PLAYING) {
        if(0!=(res=OMXToolBasicAV_RequestState(pOMXAV, OMX_StateExecuting, KD_TRUE))) {
            JoglCommon_throwNewRuntimeException(0, "Seek Execute Failed (%d)", res);
            kdThreadMutexUnlock(pOMXAV->mutex);
            return;
        }
    }
    kdThreadMutexUnlock(pOMXAV->mutex);
}

GLuint OMXToolBasicAV_GetNextTextureID(OMXToolBasicAV_t * pOMXAV, int blocking) {
    GLuint texID = 0;
    int idx = pOMXAV->glPos;
    kdThreadMutexLock(pOMXAV->mutex);

    if(pOMXAV->status==OMXAV_PLAYING) {
        int next = (pOMXAV->omxPos + 1) % pOMXAV->vBufferNum;
        
        DBG_PRINT2("GetNextTexture A avail %d, filled %d, pos o:%d g:%d\n", 
                   pOMXAV->available, pOMXAV->filled, pOMXAV->omxPos, pOMXAV->glPos);

        while (pOMXAV->filled < pOMXAV->vBufferNum)
        {
            int attr;
            if ( !_hasEGLSync || (
                 eglGetSyncAttribKHR(pOMXAV->dpy, pOMXAV->buffers[pOMXAV->omxPos].sync, EGL_SYNC_STATUS_KHR, &attr) &&
                 attr == EGL_SIGNALED_KHR )
               )
            {
                DBG_PRINT2( "GetNextTexture p2.1 attr 0x%X\n", attr);
                // OpenGL has finished rendering with this texture, so we are free
                // to make OpenMAX IL fill it with new data.
                OMX_FillThisBuffer(pOMXAV->comp[OMXAV_H_VSCHEDULER], pOMXAV->buffers[pOMXAV->omxPos].omxBufferHeader);
                DBG_PRINT2( "GetNextTexture p2.2\n");
                pOMXAV->omxPos = next;
                next = (pOMXAV->omxPos + 1) % pOMXAV->vBufferNum;
                pOMXAV->filled++;
                if(!blocking) {
                    break;
                }
            } else {
                DBG_PRINT2( "GetNextTexture p2.3\n");
                break;
            }
        }
    }
    if (pOMXAV->available > 1)
    {
        DBG_PRINT2("GetNextTexture p3.1\n");
        // We want to make sure that the previous eglImage
        // has finished, so insert a fence command into the
        // command stream to make sure that any rendering using
        // the previous eglImage has finished.
        //
        // Only move on to rendering the next image if the insertion
        // was successfull.
        if (!_hasEGLSync || eglSignalSyncKHR(pOMXAV->dpy, pOMXAV->buffers[pOMXAV->glPos].sync, EGL_UNSIGNALED_KHR))
        {
            DBG_PRINT2( "GetNextTexture p3.2\n");
            pOMXAV->available--;
            pOMXAV->filled--;
            pOMXAV->glPos = (pOMXAV->glPos + 1) % pOMXAV->vBufferNum;
            idx = pOMXAV->glPos;
        }
    }

    texID = pOMXAV->available ? pOMXAV->buffers[idx].tex : 0;
    DBG_PRINT2( "GetNextTexture B avail %d, filled %d, pos o:%d g:%d t:%d\n", 
                pOMXAV->available, pOMXAV->filled, pOMXAV->omxPos, pOMXAV->glPos, texID);

    kdThreadMutexUnlock(pOMXAV->mutex);
    return texID;
}

KDint64 OMXToolBasicAV_GetCurrentPosition(OMXToolBasicAV_t * pOMXAV) {
    KDint64 res = 0L;
    if(NULL==pOMXAV) {
        JoglCommon_throwNewRuntimeException(0, "OMX instance null\n");
        return res;
    }
    if(pOMXAV->status<=OMXAV_INIT) {
        JoglCommon_throwNewRuntimeException(0, "OMX invalid status: %d <= INIT\n", pOMXAV->status);
        return res;
    }
    kdThreadMutexLock(pOMXAV->mutex);

    res = GetClockPosition(pOMXAV);

    kdThreadMutexUnlock(pOMXAV->mutex);

    return res;
}

void OMXToolBasicAV_DestroyInstance(OMXToolBasicAV_t * pOMXAV)
{
    if(NULL==pOMXAV) return;

    kdThreadMutexLock(pOMXAV->mutex);
    DestroyInstanceUnlock(pOMXAV);
}

#if defined(SELF_TEST)

#include <KD/NV_extwindowprops.h>

static PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR;

int ModuleTest()
{
    int i;
    OMXToolBasicAV_t * pOMXAV = NULL;
    GLuint tex; EGLImageKHR image; EGLSyncKHR sync;
    KDchar file[512];
    EGLint attrib = EGL_NONE;

#if 0    
    const EGLint s_configAttribs[] = {
        EGL_RED_SIZE,           1,
        EGL_GREEN_SIZE,         1,
        EGL_BLUE_SIZE,          1,
        EGL_ALPHA_SIZE,         EGL_DONT_CARE,
        EGL_DEPTH_SIZE,         1,
        EGL_STENCIL_SIZE,       EGL_DONT_CARE,
        EGL_SURFACE_TYPE,       EGL_WINDOW_BIT,
        EGL_RENDERABLE_TYPE,    EGL_OPENGL_ES2_BIT,
        EGL_NONE
    };
#else
    const EGLint s_configAttribs[] = {
        EGL_RED_SIZE,           5,
        EGL_GREEN_SIZE,         6,
        EGL_BLUE_SIZE,          5,
        EGL_ALPHA_SIZE,         EGL_DONT_CARE,
        EGL_DEPTH_SIZE,         16,
        EGL_STENCIL_SIZE,       EGL_DONT_CARE,
        EGL_SURFACE_TYPE,       EGL_WINDOW_BIT,
        EGL_RENDERABLE_TYPE,    EGL_OPENGL_ES2_BIT,
        EGL_NONE
    };
#endif

    const EGLint contextAttrs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE
    };

    EGLint numConfigs;
    EGLint majorVersion;
    EGLint minorVersion;

    EGLint sWidth, sHeight;

    EGLDisplay       eglDisplay;
    EGLConfig        eglConfig;
    EGLContext       eglContext;
    EGLSurface       eglWindowSurface;
    KDWindow        *kdWindow;
    NativeWindowType ntWindow;

//    KDint wSize[2];

    /*
     * EGL initialisation.
     */

    eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    eglInitialize(eglDisplay, &majorVersion, &minorVersion);
    eglChooseConfig(eglDisplay, s_configAttribs, &eglConfig, 1, &numConfigs);
    kdWindow = kdCreateWindow(eglDisplay, eglConfig, KD_NULL);
    
    {
        /* Set fullscreen mode */
        KDboolean fullscreen = KD_TRUE;
        kdSetWindowPropertybv(kdWindow,
            KD_WINDOWPROPERTY_FULLSCREEN_NV, &fullscreen);
    }

    kdRealizeWindow(kdWindow, &ntWindow);

    eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, contextAttrs);
    
    eglWindowSurface = eglCreateWindowSurface(eglDisplay, eglConfig, ntWindow, KD_NULL);
    eglMakeCurrent(eglDisplay, eglWindowSurface, eglWindowSurface, eglContext);

    printf("EGL Extensions : %s\n",eglQueryString(eglDisplay, EGL_EXTENSIONS));
    printf("EGL CLIENT APIs: %s\n",eglQueryString(eglDisplay, EGL_CLIENT_APIS));

    eglQuerySurface(eglDisplay, eglWindowSurface, EGL_WIDTH   , &sWidth);
    eglQuerySurface(eglDisplay, eglWindowSurface, EGL_HEIGHT  , &sHeight);

    /* Set up the viewport and perspective. */
    printf("screen dim %dx%d\n", sWidth, sHeight);
    glViewport(0, 0, sWidth, sHeight);

    /*
    if (argc == 2)
        kdStrcpy_s(file, 512, argv[1]);
    else */
        kdStrcpy_s(file, 512, "/Storage Card/resources/videoplayer/Luna_800x480_1_5M_H264.mp4");

    if( OMXToolBasicAV_IsFileValid(file) ) {
        fprintf(stderr, "File is invalid");
        return -1;
    }

    GETEXTENSION(PFNEGLDESTROYIMAGEKHRPROC,     eglDestroyImageKHR);

    pOMXAV = OMXToolBasicAV_CreateInstance(eglDisplay);
    if(OMXToolBasicAV_SetStream(pOMXAV, file)) {
        return -1;
    }
    printf("movie dim %dx%d\n", pOMXAV->width, pOMXAV->height);

    glActiveTexture(GL_TEXTURE0);
    printf("i1: 0x%X\n", glGetError());
    glEnable(GL_TEXTURE_2D);
    printf("i2: 0x%X\n", glGetError());

    for (i = 0; i < 3; i++)
    {
        printf("0: 0x%X\n", glGetError());
        glGenTextures(1, &tex);
        printf("1: tex: %d, e 0x%X\n", tex, glGetError());
        glBindTexture(GL_TEXTURE_2D, tex);
        printf("2: 0x%X\n", glGetError());

        // create space for buffer with a texture
        glTexImage2D(
                GL_TEXTURE_2D,    // target
                0,                // level
                GL_RGBA,          // internal format
                pOMXAV->width,    // width
                pOMXAV->height,   // height
                0,                // border
                GL_RGBA,          // format
                GL_UNSIGNED_BYTE, // type
                NULL);            // pixels -- will be provided later
        printf("3: 0x%X\n", glGetError());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        printf("4: 0x%X\n", glGetError());

        // create EGLImage from texture
        image = eglCreateImageKHR(
                eglDisplay,
                eglContext,
                EGL_GL_TEXTURE_2D_KHR,
                (EGLClientBuffer)(tex),
                &attrib);
        if (!image)
        {
            printf("eglGetError: 0x%x\n", eglGetError());
            printf("ERROR creating EglImage\n");
            return -1;
        }
        printf("5 eglGetError: 0x%x\n", eglGetError());

        sync = eglCreateSyncKHR(
            eglDisplay, EGL_SYNC_FENCE_KHR, &attrib);

        printf("6 eglGetError: 0x%x\n", eglGetError());

        if(OMXToolBasicAV_SetStreamEGLImageTexture2D(pOMXAV, i, tex, image, sync)) {
            return -1;
        }
    }
    
    printf("7\n");
    if( OMXToolBasicAV_ActivateStream(eglDisplay, pOMXAV) ) {
        return -1;
    }

    printf("8\n");
    if( OMXToolBasicAV_PlayStart(eglDisplay, pOMXAV) ) {
        return -1;
    }

    printf("8.2\n");

    i = 0;
    while (i++ < 10) {
        glClear(GL_COLOR_BUFFER_BIT);
        // set uniforms
        // set attributes
        // draw arrays ..
        eglSwapBuffers(eglDisplay, eglWindowSurface);
        printf("Sleep %d\n", i);
        usleep(1000);
    }
    
    printf("9\n");
    if( OMXToolBasicAV_PlayStop(eglDisplay, pOMXAV) ) {
        fprintf(stderr, "Err: Stop");
        return -1;
    }
    printf("A1\n");
    OMXToolBasicAV_DetachVideoRenderer(eglDisplay, pOMXAV); // Stop before ..

    printf("A2\n");
    OMXToolBasicAV_AttachVideoRenderer(eglDisplay, pOMXAV); // DetachVideoRenderer before ..

    printf("B\n");
    OMXToolBasicAV_DestroyInstance(eglDisplay, pOMXAV);

    printf("C\n");
    eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroySurface(eglDisplay, eglWindowSurface);
    eglDestroyContext(eglDisplay, eglContext);

    printf("D\n");
    kdDestroyWindow(kdWindow);

    printf("E\n");
    eglTerminate(eglDisplay);
    printf("F\n");
    eglReleaseThread();

    return 0;
}

KDint kdMain(KDint argc, const KDchar *const *argv)
// int main(int argc, const char *const *argv)
{
    return ModuleTest();
}
#endif