/*===========================================================================

FILE: OEMMediaCMX.c

SERVICES: IMEDIA interfaces

DESCRIPTION
   This file implements IMedia-based classes on CMX multimedia platform.

PUBLIC CLASSES:  Not Applicable

INITIALIZATION AND SEQUENCING REQUIREMENTS:  Not Applicable

        Copyright ?1999-2004 QUALCOMM Incorporated.
               All Rights Reserved.
            QUALCOMM Proprietary/GTDR
===========================================================================*/

/*===========================================================================

                     INCLUDE FILES FOR MODULE

===========================================================================*/
#include <stdlib.h>
#include "OEMFeatures.h"

#if defined(FEATURE_BREW_MULTIMEDIA)

#include "msm/bridle_MediaCMX.h"
#include "OEMClassIDs.h"
#include "OEMMediaFormats.h"
#include "OEMObjectMgr.h"
#include "AEECriticalSection.h"

#include "OEMMedia.h"
#include "AEE_OEMDispatch.h"
#include "AEEStdLib.h"
#include "AEEMediaFormats.h"
#include "AEEMIMETypes.h"
#include "AEEFile.h"
#include "AEE_OEM.h"
#include "AEEBitmap.h"

#include "AEESource.h"

typedef struct CMediaBg          CMediaBg;
typedef struct CMediaMIDIOutMsg  CMediaMIDIOutMsg;
typedef struct CMediaMIDIOutQCP  CMediaMIDIOutQCP;

/*===========================================================================

                      DEFINES

===========================================================================*/
#ifdef FARF
#undef FARF
#endif /* #ifdef FARF */

#if defined(AEE_SIMULATOR)
#define FARF_ALWAYS  1
#else
#define FARF_ALWAYS  0
#endif

#define FARF(x, p)  if (1 == FARF_##x) DBGPRINTF p 

#define ASSERT_FARF(x) if (!(x)) { FARF(ALWAYS,("assertion %s failed, "__FILE__":%d", #x, __LINE__)); }

#define FARF_QUEUE   1
#define FARF_SOURCE  1
#define FARF_MEDIA   1
#define OEMMM_RINGER_SILENCE_TIME      1000  // 1 sec (default)

#define OEMMM_TICK_PERIOD              1000  // 1 sec (default)
#define OEMMM_MAX_CB_PLAY              3     // simultaneous play/record cbs 
#define OEMMM_DEF_WIPE_COLOR           MAKE_RGB(0xFF, 0, 0)
#define OEMMM_DEF_TEXT_COLOR           MAKE_RGB(0, 0, 0)
//#define OEMMM_GETPMD(p)       ((CMediaBg *)(p))->u.m_pPMD

#define OEMMM_CLIENT_BUF_INIT_COUNT    3     // 1 each for image, animation, text

//$r$
// NOTE: We need to use AEECriticalSection as OEMMediaCMX.c resides in 
// user space and only callback functions get executed in CMX (system)
// context.
#if __ARMCC_VERSION >= 120000 // ADS 1.2
#pragma arm section zidata = "bridle_critical_section_zi"
#endif // __ARMCC_VERSION >= 120000 // ADS 1.2
static AEECriticalSection gMediaCMXCriticalSection;
#if __ARMCC_VERSION >= 120000 // ADS 1.2
#pragma arm section zidata
#endif // __ARMCC_VERSION >= 120000 // ADS 1.2

#define OEMMM_ENTER_CRITICAL_SECTION()     AEECriticalSection_Enter(&gMediaCMXCriticalSection); {
#define OEMMM_LEAVE_CRITICAL_SECTION()  ;} AEECriticalSection_Leave(&gMediaCMXCriticalSection);

/*===============================================================================

      CMediaCMX DECLARATIONS
      These are used by all IMedia classes implemented in this file.

===============================================================================*/

#define INHERIT_CMediaCMX(iname) \
   INHERIT_AEEMedia(iname); \
   AEEObjectHandle   m_hObject;  \
   PFNNOTIFY         m_pfnDelete; \
   OEMINSTANCE       m_hMediaCMX; \
   \
   uint32            m_dwCaps; \
   uint32            m_dwEnabledCaps; \
   int               m_nAPath; \
   boolean           m_bMute; \
   uint16            m_wVolume; \
   uint16            m_wPan; \
   uint16            m_wTempo; \
   uint16            m_wTune; \
   uint32            m_dwTickTimeMS; \
   uint16            m_wSilenceTimeMS; \
   flg               m_bStopping:1; \
   flg               m_bMIDIOut:1

OBJECT(CMediaCMX)
{
   INHERIT_CMediaCMX(IMediaMIDI);
};

// Constructor and destructor
static IBase * CMediaCMX_New(IShell * ps, AEECLSID cls, const void * pVtbl, uint32 dwSize, PFNNOTIFY pfnDelete);
static void    CMediaCMX_Delete(CMediaCMX * po);

// Interface functions
static uint32  CMediaCMX_Release(IMedia * po);
static int     CMediaCMX_SetMediaParm(IMedia * po, int nParmID, int32 p1, int32 p2);
static int     CMediaCMX_GetMediaParm(IMedia * po, int nParmID, int32 * pP1, int32 * pP2);

static int     CMediaCMX_RecordUnsupported(IMedia * po);
static int     CMediaCMX_StopUnsupported(IMedia * po);
static int     CMediaCMX_SeekUnsupported(IMedia * po, AEEMediaSeek eSeek, int32 lTimeMS);
static int     CMediaCMX_PauseUnsupported(IMedia * po);
static int     CMediaCMX_ResumeUnsupported(IMedia * po);
static int     CMediaCMX_GetTotalTimeUnsupported(IMedia * po);

// Callback from due to resume in ASIC MM Layer callback.
static void    CMediaCMX_CallbackNotify(void * pUser);

// MMLayer global static objects...
typedef struct CMediaInitFlags
{
   flg   gbCMXInit:1;
   flg   gbMediaBgInit:1;
   flg   gbMediaMIDIOutQCPInit:1;
} CMediaInitFlags;

static CMediaInitFlags  gInitFlags;

static AEECallback   gcbAEEExit;

#if defined(FEATURE_MIDI_OUT)

#define OEMMM_ADEC_MODE_DEFAULT  0  // Default type of QSynth ADEC
#define OEMMM_ADEC_MODE_ADPCM    1  // QSynth ADPCM audio decoder
#define OEMMM_ADEC_MODE_QCP      2  // QSynth QCELP audio decoder

typedef void (*PFNMIDIOUTNOTIFY)(CMediaCMX * pme, boolean bSuccess);

typedef struct CMIDIOut
{
   int               m_nRef;
   byte              m_nADECType;   // For CMX 2.x, zero. For CMX 3.x, OEMMM_ADEC_MODE_XXX
   boolean           m_bOpenPending;
   PFNMIDIOUTNOTIFY  m_pfnNotify;
   CMediaCMX *       m_pMM;
   AEECallback       m_cb;
} CMIDIOut;

static int CMIDIOut_AddRef(void);
static int CMIDIOut_Release(void);
static int CMIDIOut_Open(byte nADECType, PFNMIDIOUTNOTIFY pfn, CMediaCMX * pme);
static void CMIDIOut_OpenNotify(void * pUser);

static CMIDIOut   gsMIDIOut = { 0 };

#define CMIDIOut_IsOpen()        (gsMIDIOut.m_nRef > 0)
#define CMIDIOut_IsDefault()     (gsMIDIOut.m_nADECType == OEMMM_ADEC_MODE_DEFAULT)
#define CMIDIOut_IsQCP()         (gsMIDIOut.m_nADECType == OEMMM_ADEC_MODE_QCP || CMIDIOut_IsDefault())
#define CMIDIOut_IsADPCM()       (gsMIDIOut.m_nADECType == OEMMM_ADEC_MODE_ADPCM || CMIDIOut_IsDefault())

#endif // defined(FEATURE_MIDI_OUT)

/*===========================================================================
      MMLayer functions
===========================================================================*/

static void CMediaMMLayer_Init(IShell * ps);
static void CMediaMMLayer_AEEExitNotify(void * pUser);
static void CMediaMMLayer_SoundCB(void * pUser, AEESoundCmd eCBType, AEESoundStatus eStatus, uint32 dwParam);
static void CMediaMMLayer_InitCallback(IMedia * po, int nCmd, AEEMediaCallback * pcb, int nMax, PFNNOTIFY pfn);
static AEEMediaCallback * CMediaMMLayer_AllocCallback(AEEMediaCallback * pcb, int nMax, int nCmd, int nSubCmd);
static void CMediaMMLayer_FreeCallback(AEEMediaCallback * pcb);
static void CMediaMMLayer_CancelCallback(AEEMediaCallback * pcb, int nMax);

/*---------------------------------------------------------------------------
      Sound functions
-----------------------------------------------------------------------------*/
static void CMediaMMLayer_SoundCB(void * pUser, AEESoundCmd eCBType, AEESoundStatus eSPStatus, uint32 dwParam);
static int CMediaMMLayer_SetVolume(CMediaBg * pme, uint16 wVolume);
static int CMediaMMLayer_SetDevice(CMediaBg * pme, AEESoundDevice eDevice);
static int CMediaMMLayer_SetPath(CMediaBg * pme, int nAPath);
static int CMediaMMLayer_SetMute(CMediaBg * pme, boolean bMute);

/*---------------------------------------------------------------------------
      Media play/record
-----------------------------------------------------------------------------*/
static int CMediaMMLayer_Play(CMediaBg * pme);
static int CMediaMMLayer_PlayCodec(CMediaBg * pme, cmx_handle_type * ph, AEEMediaCallback * pcb);
static int CMediaMMLayer_Stop(CMediaBg * pme);
static int CMediaMMLayer_Seek(CMediaBg * pme, AEEMediaSeek eSeek, int32 lTimeMS);
static int CMediaMMLayer_Pause(CMediaBg * pme);
static int CMediaMMLayer_Resume(CMediaBg * pme);
static int CMediaMMLayer_GetTotalTime(CMediaBg * pme);

static int CMediaMMLayer_SetTempo(CMediaBg * pme, uint32 dwTempo);
static int CMediaMMLayer_SetTune(CMediaBg * pme, uint32 dwTune);
static int CMediaMMLayer_SetPan(CMediaBg * pme, uint32 dwTune);

static int CMediaMMLayer_QCPRecord(CMediaBg * pme, int nFormat);
static int CMediaMMLayer_PCMRecord(CMediaBg * pme);

//$r$
static void CMediaMMLayer_FlushStreamData(CMediaBg * pme);
#if defined(FEATURE_QCP)
static int CMediaMMLayer_GetQCPFormat(int nFormat, int * pCodec);
#endif // FEATURE_QCP

/*---------------------------------------------------------------------------
      Media play thru MIDIOut device
-----------------------------------------------------------------------------*/
static int CMediaMMLayer_MIDIOutMsg(CMediaMIDIOutMsg * pme);

static int CMediaMMLayer_MIDIOutQCPPlay(CMediaMIDIOutQCP * pme);
static int CMediaMMLayer_MIDIOutQCPStop(CMediaMIDIOutQCP * pme, uint32 dwChannel);
static int CMediaMMLayer_MIDIOutQCPPause(CMediaMIDIOutQCP * pme);
static int CMediaMMLayer_MIDIOutQCPResume(CMediaMIDIOutQCP * pme);
static int CMediaMMLayer_MIDIOutQCPSetVolume(CMediaMIDIOutQCP * pme, uint32 dwChannel, uint16 wVolume);
static int CMediaMMLayer_MIDIOutQCPSetPan(CMediaMIDIOutQCP * pme, uint32 dwChannel, uint16 wPan);

#if defined(FEATURE_WEBAUDIO)

//Stuff needed to draw the images
//Image draw/clear flags
#define  OEMMM_DISP_CLEAR    0x01
#define  OEMMM_DISP_DRAW     0x02

static void     CMXImage_Notify(void * pUser);
static void     AllocJPEGBuf(void * pUser);
static void     FreeJPEGBuf(void * pUser);
static void     AllocClientBuf(void * pUser);
static void     FreeClientBuf(void * pUser);
static void     PICInitialize(void * pUser);
static void     PICRelease(void * pUser);
static void     ProcessPMDText(void * pUser);
static void     BMPCopyBuffer(void * pUser);
static void     PNGCopyBuffer(void * pUser);
#ifdef FEATURE_AUDIO_POSTCARD
static void     JPEGCopyBuffer(void * pUser);
#endif /* FEATURE_AUDIO_POSTCARD */
static void     SAFCreateDIB(void * pUser);
static void     SAFLoadFrameToDIB(void * pUser);
#endif // defined(FEATURE_WEBAUDIO)

/*===============================================================================

      CMediaBg: Background Audio (MIDI, MP3, QCP. Includes PMD) related.

===============================================================================*/
typedef struct CQueue
{
   uint8 *  pBuffer;    // Size of the queue
   int32    nMaxSize;   // Max size (bytes) of the queue
   int32    nWrite;     // Write pointer
   int32    nRead;      // Read pointer
   int32    nFill;      // Size (bytes) of data filled in the queue
   boolean  bMutex;     // Specifies if mutex lock is required for operations
} CQueue;

static int     CQueue_New(int32 nSize, boolean bMutex, CQueue ** ppQ);
static void    CQueue_Delete(CQueue * pme);
static int32   CQueue_Put(CQueue * pme, uint8 * pData, int32 nSize);
static int32   CQueue_Get(CQueue * pme, uint8 * pData, int32 nSize);
#define        CQueue_MaxSize(pme)           ((pme)->nMaxSize)
#define        CQueue_Size(pme)              ((pme)->nFill)
#define        CQueue_IsEmpty(pme)           (0 == (pme)->nFill)
#define        CQueue_IsFull(pme)            ((pme)->nFill >= (pme)->nMaxSize)
#define        CQueue_IsPutOk(pme, nSize)    ((pme)->nFill + (nSize) <= (pme)->nMaxSize)
#define        CQueue_Flush(pme)             ((pme)->nWrite = (pme)->nRead = (pme)->nFill = 0)
#define        CQueue_GetBuffer(pme)         ((pme)->pBuffer)

#define OEMMM_CMX_REQ_SIZE_MAX         (4 * sizeof(CMXDataRequest))  // CMX request buffers
#define OEMMM_BUFFER_UNIT_SIZE         (8820)                        // IMedia data fetch unit size
#define OEMMM_BUFFER_SIZE_MAX          (4 * OEMMM_BUFFER_UNIT_SIZE)  // IMedia data buffer size (default)
#define OEMMM_ISSTREAMING(pme)         ((pme)->m_bStreaming)

typedef struct CMediaMP3
{
   AEEMediaMP3Spec   m_spec; // MP3
} CMediaMP3;

typedef struct CMediaQCP
{
   byte  m_nFormat;     // QCP
} CMediaQCP;

typedef struct CMediaADPCM
{
   AEEMediaWaveSpec  m_spec; // IMA-ADPCM
} CMediaADPCM;


#ifdef FEATURE_WEBAUDIO

typedef struct TextBlock
{
   AECHAR *    m_text;
   uint16      m_length;
   uint16      m_x;
   uint16      m_y;
   uint32      m_style;
   RGBVAL      m_color;
   RGBVAL      m_wipeColor;
   uint16      m_extent;
   uint16      m_row;
   struct TextBlock * m_next;
} TextBlock;

typedef struct CMediaPMD
{
   AEERect     m_rectImage;
   AEERect     m_rectText;

   cmx_af_char_set_enum_type  m_char_set;  /* Character code set to use for text */

   uint16      m_numToWipe;
   TextBlock * m_textBlock;
   cmx_af_text_ctl_type m_textCtl;
   AEECallback m_cbPMDText;  // To schedule text processing

   cmx_af_pic_data_type m_picData;
   cmx_af_picture_status_type m_picStatus;
   AEECallback m_cbPicInitialize;   // To schedule Initialization of picture bitmap in user context
   AEECallback m_cbPicRelease;      // To schedule release of picture bitmap in user context

   // BMP support
   AEECallback m_cbCopyBMPBuf;  // To schedule bmp processing

   // PNG support
   byte *      m_pPNGBuf;
   uint32      m_pPNGBufSize;
   AEECallback m_cbCopyPNGBuf;  // To schedule png processing

   // JPEG Buffer
   byte *      m_pJPEGBuf;
   uint32      m_pJPEGBufSize;
   AEECallback m_cbCopyJPEGBuf; // To schedule jpeg processing
      
   cmx_client_buf_req_cb_func_ptr_type m_cbJPEGBufAllocNotify;
   void*       m_pJPEGClientData;
   AEECallback m_cbJPEGBufAlloc;  // To schedule buffer creation in user context
   AEECallback m_cbJPEGBufFree;   // To schedule buffer release in user context

   cmx_saf_info_type m_safInfo;
   AEECallback m_cbSAFCreateDIB;  // To schedule SAF processing
   cmx_saf_data_type m_safData;
   AEECallback m_cbSAFLoadFrame;  // To schedule SAF processing

   IDisplay *  m_pIDisp;
   IDIB *      m_pISafBitmap;
   int         m_SAFBitDepth;

   // This is the display information for the animation
   AEERect     m_safDispRect;
   AEERect     m_safOffsetRect;

   IDIB *      m_pIPicBitmap;

   // This is the display information for the picture
   AEERect     m_picDispRect;
   AEERect     m_picOffsetRect;

   IDIB *      m_pIDispBitmap;

   AEECallback m_cbPMDImage;  // To schedule image drawing
   AEERect     m_dispRect;
   AEERect     m_offsetRect;
   uint16      m_cxScreen;
   uint16      m_cyScreen;
   byte        m_clrDepth;
   byte        m_xJustPcent_Pixel; // Holds percentage or pixel offset
   byte        m_yJustPcent_Pixel; 
   byte        m_fJust;
   
   uint8 **    m_ppClientBufs;  // cmx buffers allocated in user mode
   int         m_numClientBufs;
   int         m_lastBufAllocated; // indicates most recent buffer allocated, or -1 if allocation failed.
   uint32      m_ClientBufSize;    // size of buffer being requested
   uint8 *     m_ClientBufSent;    // address of a buffer sent by cmx to use or to free
   cmx_client_buf_req_cb_func_ptr_type m_cbClientBufAllocNotify;   
   void*       m_pBufClientData;
   AEECallback m_cbClientBufAlloc;  // To schedule buffer creation in user context
   AEECallback m_cbClientBufFree;   // To schedule buffer release in user context
   
   flg         m_fTextDraw:2;
   flg         m_fPicDraw:2;
   flg         m_fSafDraw:2;
   flg         m_bHeaderRead:1;
   flg         m_displayedSaf:1;  // This flag is true if an animation has ever been displayed.
   flg         m_transparency:1;  // This flag is true if an animation contains transparency. 
   flg         m_displayedPic:1;  // This flag is true if an image has ever been displayed.
   
   
} CMediaPMD;
#endif // FEATURE_WEBAUDIO

struct CMediaBg
{
   INHERIT_CMediaCMX(IMediaMIDI);

   union u
   {
      CMediaMP3 *    m_pMP3;
      CMediaQCP *    m_pQCP;
      CMediaADPCM *  m_pADPCM;
#ifdef FEATURE_WEBAUDIO
      CMediaPMD *    m_pPMD;
#endif // FEATURE_WEBAUDIO
   } u;

   AEEMediaCallback  m_cbPlay[OEMMM_MAX_CB_PLAY];
   AEEMediaCallback  m_cbCommand;
   AEEMediaCallback  m_cbGetTotalTime;

   ISound *          m_pISound;

   CQueue *          m_pqCMXDataReq;
   AEECallback *     m_pcbCMXDataReq;
   CQueue *          m_pqMediaData;
   AEECallback *     m_pcbMediaData;
   uint8 *           m_pSrcBuffer;
   uint32            m_dwSrcBufferSize;
   flg               m_bStreaming:1;
   flg               m_bEOS:1;
   flg               m_bChannelShare;
   void   *          m_pSequence;
};

void IMediaBg_Init(IShell * ps);
int  IMediaBg_New(IShell * ps, AEECLSID cls, void **ppif);

static int     CMediaBg_SetMediaParm(IMedia * po, int nParmID, int32 p1, int32 p2);
static int     CMediaBg_GetMediaParm(IMedia * po, int nParmID, int32 * pP1, int32 * pP2);
static int     CMediaBg_Play(IMedia * po);
static int     CMediaBg_Record(IMedia * po);
static int     CMediaBg_Stop(IMedia * po);
static int     CMediaBg_Seek(IMedia * po, AEEMediaSeek eSeek, int32 lTimeMS);
static int     CMediaBg_Pause(IMedia * po);
static int     CMediaBg_Resume(IMedia * po);
static int     CMediaBg_GetTotalTime(IMedia * po);

static void    CMediaBg_CallbackNotify(void * pUser);
static void    CMediaBg_Delete(CMediaBg * pme);

static const VTBL(IMediaMIDI) gMediaBgFuncs =
{
   AEEMedia_AddRef,
   CMediaCMX_Release,
   AEEMedia_QueryInterface,
   AEEMedia_RegisterNotify,
   CMediaBg_SetMediaParm,
   CMediaBg_GetMediaParm,
   CMediaBg_Play,
   CMediaBg_Record,
   CMediaBg_Stop,
   CMediaBg_Seek,
   CMediaBg_Pause,
   CMediaBg_Resume,
   CMediaBg_GetTotalTime,
   AEEMedia_GetState
};

/*===========================================================================

                      IMediaMIDIOutMsg DECLARATIONS

===========================================================================*/
struct CMediaMIDIOutMsg
{
   INHERIT_CMediaCMX(IMediaMIDIOutMsg);

   uint8 *           m_pMsg;
   uint32            m_dwSize;

   AEEMediaCallback  m_cbPlay[OEMMM_MAX_CB_PLAY];
   AEEMediaCallback  m_cbCommand;
};

void IMediaMIDIOutMsg_Init(IShell * ps);
int  IMediaMIDIOutMsg_New(IShell * ps, AEECLSID cls, void **ppif);

static int CMediaMIDIOutMsg_SetMediaParm(IMedia * po, int nParmID, int32 p1, int32 p2);
static int CMediaMIDIOutMsg_GetMediaParm(IMedia * po, int nParmID, int32 * pP1, int32 * pP2);
static int CMediaMIDIOutMsg_Play(IMedia * po);

static void CMediaMIDIOutMsg_Delete(CMediaMIDIOutMsg * pme);

static const VTBL(IMediaMIDIOutMsg) gMediaMIDIOutMsgFuncs =
{
   AEEMedia_AddRef,
   CMediaCMX_Release,
   AEEMedia_QueryInterface,
   AEEMedia_RegisterNotify,
   CMediaMIDIOutMsg_SetMediaParm,
   CMediaMIDIOutMsg_GetMediaParm,
   CMediaMIDIOutMsg_Play,
   CMediaCMX_RecordUnsupported,
   CMediaCMX_StopUnsupported,
   CMediaCMX_SeekUnsupported,
   CMediaCMX_PauseUnsupported,
   CMediaCMX_ResumeUnsupported,
   CMediaCMX_GetTotalTimeUnsupported,
   AEEMedia_GetState
};

/*===========================================================================

                      IMediaMIDIOutQCP DECLARATIONS

===========================================================================*/
struct CMediaMIDIOutQCP
{
   INHERIT_CMediaCMX(IMediaMIDIOutQCP);

   uint32            m_dwChannel;

   AEEMediaCallback  m_cbPlay[OEMMM_MAX_CB_PLAY];
   AEEMediaCallback  m_cbCommand;
};

void IMediaMIDIOutQCP_Init(IShell * ps);
int  IMediaMIDIOutQCP_New(IShell * ps, AEECLSID cls, void **ppif);

static int CMediaMIDIOutQCP_SetMediaParm(IMedia * po, int nParmID, int32 p1, int32 p2);
static int CMediaMIDIOutQCP_GetMediaParm(IMedia * po, int nParmID, int32 * pP1, int32 * pP2);
static int CMediaMIDIOutQCP_Play(IMedia * po);
static int CMediaMIDIOutQCP_Stop(IMedia * po);
static int CMediaMIDIOutQCP_Pause(IMedia * po);
static int CMediaMIDIOutQCP_Resume(IMedia * po);

static void CMediaMIDIOutQCP_Delete(CMediaMIDIOutQCP * pme);

static const VTBL(IMediaMIDIOutQCP) gMediaMIDIOutQCPFuncs =
{
   AEEMedia_AddRef,
   CMediaCMX_Release,
   AEEMedia_QueryInterface,
   AEEMedia_RegisterNotify,
   CMediaMIDIOutQCP_SetMediaParm,
   CMediaMIDIOutQCP_GetMediaParm,
   CMediaMIDIOutQCP_Play,
   CMediaCMX_RecordUnsupported,
   CMediaMIDIOutQCP_Stop,
   CMediaCMX_SeekUnsupported,
   CMediaMIDIOutQCP_Pause,
   CMediaMIDIOutQCP_Resume,
   CMediaCMX_GetTotalTimeUnsupported,
   AEEMedia_GetState
};

/*===============================================================================

      COMMON FUNCTION DEFINITIONS
      Can be used by all IMedia classes implemented in this file

===============================================================================*/
/*==================================================================
   Constructor
==================================================================*/
static IBase * CMediaCMX_New(IShell * ps, AEECLSID cls, const void * pVtbl, uint32 dwSize, PFNNOTIFY pfnDelete)
{
   IMedia *       po;
   CMediaCMX *    pme;

   // Alloc memory for the object
   po = (IMedia *)AEE_NewClass((IBaseVtbl *)pVtbl, dwSize);
   if (!po)
      return NULL;

   pme = (CMediaCMX *)po;

   // Call base class constructor
   if (SUCCESS != AEEMedia_New(po, ps, cls))
      goto Done;

   if (SUCCESS != OEMMEDIA_New(cls, &pme->m_hMediaCMX))
      goto Done;

   // Initialize MM Layer
   CMediaMMLayer_Init(ps);

   pme->m_pfnDelete = pfnDelete;

   // Register the object with ObjectMgr...
   if (AEEObjectMgr_Register(pme, &pme->m_hObject))
      goto Done;

   pme->m_dwCaps |= MM_CAPS_AUDIO;
   if (pme->m_clsSelf == AEECLSID_MEDIAPMD)
      pme->m_dwCaps |= (MM_CAPS_VIDEO | MM_CAPS_TEXT);
   pme->m_dwEnabledCaps = pme->m_dwCaps;

   pme->m_wVolume = (3 * AEE_MAX_VOLUME) / 4;
   pme->m_nAPath = MM_APATH_LOCAL;
   pme->m_wPan = 64;

   return (IBase *)po;

Done:
   CMediaCMX_Delete(pme);
//   FREE(pme);
   return NULL;
}

/*==================================================================
   Destructor
==================================================================*/
static void CMediaCMX_Delete(CMediaCMX * pme)
{
   if (pme->m_pfnDelete) {
      BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnDelete);
      pme->m_pfnDelete(pme);
   }

   AEEObjectMgr_Unregister(pme->m_hObject);

   // Call base class destructor
   AEEMedia_Delete((IMedia *)pme);

   OEMMEDIA_Delete(pme->m_hMediaCMX);

   FREE(pme);
}

/*==================================================================

==================================================================*/
static uint32 CMediaCMX_Release(IMedia * po)
{
   int32    nRef = (int32)AEEMedia_Release(po);

   if (!nRef)
      CMediaCMX_Delete((CMediaCMX *)po);

   return nRef;
}

/*==================================================================

==================================================================*/
static int CMediaCMX_SetMediaParm(IMedia * po, int nParmID, int32 p1, int32 p2)
{
   CMediaCMX * pme = (CMediaCMX *)po;
   int         nRet = SUCCESS;


   if (nParmID == MM_PARM_MEDIA_DATA)
      return AEEMedia_SetMediaParm(po, nParmID, p1, p2);

   // No need to alloc a command callback for ISound operations since the callback
   // from ISound uniquely identifies this operation and also
   // gets called in BREW context.
   // These are MM_PARM_VOLUME, MM_PARM_PATH, MM_PARM_MUTE.
  
   switch (nParmID)
   {
      case MM_PARM_ENABLE:
         if (p1)
         {
            uint32   dw = pme->m_dwCaps & p1;
            if (dw)
               pme->m_dwEnabledCaps = dw;
            else
               nRet = EBADPARM;
         }
         break;

      default:
         nRet = EUNSUPPORTED;
         break;
   }

   return nRet;
}

/*==================================================================

==================================================================*/
static int CMediaCMX_GetMediaParm(IMedia * po, int nParmID, int32 * pP1, int32 * pP2)
{
   CMediaCMX * pme = (CMediaCMX *)po;
   int         nRet = SUCCESS;

   if (nParmID == MM_PARM_MEDIA_DATA || nParmID == MM_PARM_CLSID)
      return AEEMedia_GetMediaParm(po, nParmID, pP1, pP2);

   switch (nParmID)
   {
      case MM_PARM_CAPS:
         if (pP1)
            *pP1 = (int32)pme->m_dwCaps;
         if (pP2)
            *pP2 = 0;
         break;

      case MM_PARM_ENABLE:
         if (pP1)
            *pP1 = (int32)pme->m_dwEnabledCaps;
         if (pP2)
            *pP2 = 0;
         break;

      case MM_PARM_CHANNEL_SHARE:
         if (pP1)
            *(boolean *)pP1 = FALSE;
         break;

      default:
         nRet = EUNSUPPORTED;
         break;
   }

   return nRet;
}

/*==================================================================
   Unsupported functions -- used in vtbl of IMedia classes
==================================================================*/
static int CMediaCMX_RecordUnsupported(IMedia * po)
{
   return EUNSUPPORTED;
}

static int CMediaCMX_StopUnsupported(IMedia * po)
{
   return EUNSUPPORTED;
}

static int CMediaCMX_SeekUnsupported(IMedia * po, AEEMediaSeek eSeek, int32 lTimeMS)
{
   return EUNSUPPORTED;
}

static int CMediaCMX_PauseUnsupported(IMedia * po)
{
   return EUNSUPPORTED;
}

static int CMediaCMX_ResumeUnsupported(IMedia * po)
{
   return EUNSUPPORTED;
}

static int CMediaCMX_GetTotalTimeUnsupported(IMedia * po)
{
   return EUNSUPPORTED;
}

/*==================================================================

   Callback Notification Functions: called in BREW context after
   AEE_ResumeCallback()

==================================================================*/
/*==================================================================
   Media callback notify -- called in BREW context.
==================================================================*/
void CMediaCMX_CallbackNotify(void * pUser)
{
   AEEMediaCallback *   pcb = (AEEMediaCallback *)pUser;
   CMediaCMX *          pme;
   AEEMediaCallback     mcb;

   if (!pcb)
      return;

   MEMCPY(&mcb, pcb, sizeof(AEEMediaCallback));
   CMediaMMLayer_FreeCallback(pcb);

   pme = (CMediaCMX *)mcb.cmdNotify.pIMedia;
   if (!pme)
      return;

   AEEMedia_CallbackNotify((AEEMedia *)pme, &mcb);
}

/*===============================================================================

                     CMediaBg PUBLIC FUNCTION DEFINITIONS

===============================================================================*/
/*==================================================================
   Called only once during AEE initialization
==================================================================*/
void IMediaBg_Init(IShell * ps)
{
   if (gInitFlags.gbMediaBgInit)
      return;

   gInitFlags.gbMediaBgInit = TRUE;

   // If found in the mod table, then register the formats...

   if (CMediaFormat_InModTable(AEECLSID_MEDIAMIDI, (void *)IMediaBg_Init, NULL))
      AEEMedia_Init(ps, MT_AUDIO_MIDI, AEECLSID_MEDIAMIDI);

   if (CMediaFormat_InModTable(AEECLSID_MEDIAMP3, (void *)IMediaBg_Init, NULL))
      AEEMedia_Init(ps, MT_AUDIO_MP3, AEECLSID_MEDIAMP3);

   if (CMediaFormat_InModTable(AEECLSID_MEDIAPMD, (void *)IMediaBg_Init, NULL))
   {
      AEEMedia_Init(ps, MT_VIDEO_PMD, AEECLSID_MEDIAPMD);
      AEEMedia_Init(ps, "video/mld", AEECLSID_MEDIAPMD);
   }

   if (CMediaFormat_InModTable(AEECLSID_MEDIAAAC, (void *)IMediaBg_Init, NULL))
      AEEMedia_Init(ps, MT_AUDIO_AAC, AEECLSID_MEDIAAAC);
   
   if (CMediaFormat_InModTable(AEECLSID_MEDIAIMELODY, (void *)IMediaBg_Init, (void *)NULL))
         AEEMedia_Init(ps, MT_AUDIO_IMELODY, AEECLSID_MEDIAIMELODY);

   if (CMediaFormat_InModTable(AEECLSID_MEDIAAMR, (void *)IMediaBg_Init, (void *)NULL))
         AEEMedia_Init(ps, MT_AUDIO_AMR, AEECLSID_MEDIAAMR);

   if (CMediaFormat_InModTable(AEECLSID_MEDIAMMF, (void *)IMediaBg_Init, (void *)NULL))
         AEEMedia_Init(ps, MT_AUDIO_MMF, AEECLSID_MEDIAMMF);

   if (CMediaFormat_InModTable(AEECLSID_MEDIAPHR, (void *)IMediaBg_Init, (void *)NULL))
         AEEMedia_Init(ps, MT_AUDIO_PHR, AEECLSID_MEDIAPHR);

   if (CMediaFormat_InModTable(AEECLSID_MEDIAXMF, (void *)IMediaBg_Init, (void *)NULL))
         AEEMedia_Init(ps, MT_AUDIO_XMF, AEECLSID_MEDIAXMF);

//   if (CMediaFormat_InModTable(AEECLSID_MEDIADLS, (void *)IMediaBg_Init, (void *)NULL))
//         AEEMedia_Init(ps, MT_AUDIO_DLS, AEECLSID_MEDIADLS);
   // Initialize ASIC MM Layer here...
   CMediaMMLayer_Init(ps);
}

/*==================================================================
   Called by ISHELL_CreateInstance()
==================================================================*/
int IMediaBg_New(IShell * ps, AEECLSID cls, void **ppif)
{
   int         nRet;
   CMediaBg *  pme;

   // Create the object
   if (ppif)
   {
      pme = *ppif = CMediaCMX_New(ps, cls, &gMediaBgFuncs, sizeof(CMediaBg), (PFNNOTIFY)CMediaBg_Delete);
      nRet = pme ? SUCCESS : ENOMEMORY;
   }
   else {
      nRet = EBADPARM;
      pme = 0;
   }

   if (nRet == SUCCESS)
   {
      AEESoundMethod eMethod;
      AEESoundInfo   si;
      // Create ISound, register callback notify, and set method based on media.
      pme->m_pISound = NULL;
      if (ISHELL_CreateInstance(pme->m_pIShell, AEECLSID_SOUND, (void **)&pme->m_pISound))
         goto Done;
      ISOUND_RegisterNotify(pme->m_pISound, CMediaMMLayer_SoundCB, (void *)pme->m_hObject);
      if (AEECLSID_MEDIAQCP == cls || AEECLSID_MEDIAAMR == cls)
         eMethod = AEE_SOUND_METHOD_VOICE;
      else
         eMethod = AEE_SOUND_METHOD_MIDI;
      ISOUND_Get(pme->m_pISound, &si);
      si.eMethod = eMethod;
      ISOUND_Set(pme->m_pISound, &si);

      switch (cls)
      {
         case AEECLSID_MEDIAMP3:
            pme->u.m_pMP3 = MALLOC(sizeof(CMediaMP3));
            if (!pme->u.m_pMP3)
               goto Done;
            break;

         case AEECLSID_MEDIAQCP:
            pme->u.m_pQCP = MALLOC(sizeof(CMediaQCP));
            if (!pme->u.m_pQCP)
               goto Done;
            pme->u.m_pQCP->m_nFormat = MM_QCP_FORMAT_FIXED_FULL_13K;
            break;

         case AEECLSID_MEDIAADPCM:
            pme->u.m_pADPCM = MALLOC(sizeof(CMediaADPCM));
            if (!pme->u.m_pADPCM)
               goto Done;
            break;

         case AEECLSID_MEDIAPMD:
         {
#if defined(FEATURE_WEBAUDIO)

            AEEDeviceInfo  di;

            pme->u.m_pPMD = MALLOC(sizeof(CMediaPMD));
            if (!pme->u.m_pPMD)
               goto Done;
          
            ISHELL_GetDeviceInfo(pme->m_pIShell, &di);
            pme->u.m_pPMD->m_rectImage.dx = di.cxScreen;
            pme->u.m_pPMD->m_rectImage.dy = di.cyScreen;
            pme->u.m_pPMD->m_rectText.dx = di.cxScreen;
            pme->u.m_pPMD->m_rectText.dy = di.cyScreen;


            CALLBACK_Init(&pme->u.m_pPMD->m_cbPMDImage, CMXImage_Notify, pme);
            CALLBACK_Init(&pme->u.m_pPMD->m_cbJPEGBufAlloc, AllocJPEGBuf, pme);
            CALLBACK_Init(&pme->u.m_pPMD->m_cbJPEGBufFree, FreeJPEGBuf, pme);            
            CALLBACK_Init(&pme->u.m_pPMD->m_cbClientBufAlloc, AllocClientBuf, pme);
            CALLBACK_Init(&pme->u.m_pPMD->m_cbClientBufFree, FreeClientBuf, pme);
            CALLBACK_Init(&pme->u.m_pPMD->m_cbPicInitialize, PICInitialize, pme);
            CALLBACK_Init(&pme->u.m_pPMD->m_cbPicRelease, PICRelease, pme);
            CALLBACK_Init(&pme->u.m_pPMD->m_cbPMDText, ProcessPMDText, pme);
            CALLBACK_Init(&pme->u.m_pPMD->m_cbCopyBMPBuf, BMPCopyBuffer, pme);
            CALLBACK_Init(&pme->u.m_pPMD->m_cbCopyPNGBuf, PNGCopyBuffer, pme);
            CALLBACK_Init(&pme->u.m_pPMD->m_cbCopyJPEGBuf, JPEGCopyBuffer, pme);
            CALLBACK_Init(&pme->u.m_pPMD->m_cbSAFCreateDIB, SAFCreateDIB, pme);
            CALLBACK_Init(&pme->u.m_pPMD->m_cbSAFLoadFrame, SAFLoadFrameToDIB, pme);            
            pme->u.m_pPMD->m_numToWipe = 0;
            pme->u.m_pPMD->m_textBlock = NULL;
            MEMSET(&pme->u.m_pPMD->m_textCtl, 0, sizeof(cmx_af_text_ctl_type));
            MEMSET(&pme->u.m_pPMD->m_picData, 0, sizeof(cmx_af_pic_data_type));
            MEMSET(&pme->u.m_pPMD->m_picStatus, 0, sizeof(cmx_af_picture_status_type));
            MEMSET(&pme->u.m_pPMD->m_safInfo, 0, sizeof(cmx_saf_info_type));
            MEMSET(&pme->u.m_pPMD->m_safData, 0, sizeof(cmx_saf_data_type));            
            pme->u.m_pPMD->m_pPNGBuf = NULL;
            pme->u.m_pPMD->m_pPNGBufSize = 0;
            pme->u.m_pPMD->m_pJPEGBuf = NULL;
            pme->u.m_pPMD->m_pJPEGBufSize = 0;
            pme->u.m_pPMD->m_cbJPEGBufAllocNotify = NULL;
            pme->u.m_pPMD->m_pJPEGClientData = NULL;
            pme->u.m_pPMD->m_fTextDraw = OEMMM_DISP_DRAW;
            pme->u.m_pPMD->m_pISafBitmap = NULL;
            pme->u.m_pPMD->m_pIPicBitmap = NULL;
            pme->u.m_pPMD->m_pIDispBitmap = NULL;
            pme->u.m_pPMD->m_displayedPic = FALSE;
            pme->u.m_pPMD->m_displayedSaf = FALSE;
            pme->u.m_pPMD->m_transparency = FALSE;

            pme->u.m_pPMD->m_fPicDraw = OEMMM_DISP_CLEAR | OEMMM_DISP_DRAW;
            pme->u.m_pPMD->m_fSafDraw = OEMMM_DISP_CLEAR | OEMMM_DISP_DRAW;

            if (ISHELL_CreateInstance(pme->m_pIShell, AEECLSID_DISPLAY, (void **) &pme->u.m_pPMD->m_pIDisp))
            {
               FREEIF(pme->u.m_pPMD);
               goto Done;
            }
            
            pme->u.m_pPMD->m_ppClientBufs = MALLOC(sizeof(uint8**) * OEMMM_CLIENT_BUF_INIT_COUNT);
            if (!pme->u.m_pPMD->m_ppClientBufs)
            {
               MM_RELEASEIF(pme->u.m_pPMD->m_pIDisp);
               FREEIF(pme->u.m_pPMD);
               goto Done;
            }
            pme->u.m_pPMD->m_numClientBufs = OEMMM_CLIENT_BUF_INIT_COUNT;
            pme->u.m_pPMD->m_lastBufAllocated = -1;
            pme->u.m_pPMD->m_ClientBufSize = 0;
            pme->u.m_pPMD->m_ClientBufSent = 0;
            pme->u.m_pPMD->m_cbClientBufAllocNotify = NULL;
			break;
#else
			nRet = EUNSUPPORTED;
			goto Done;
#endif // defined(FEATURE_WEBAUDIO)            
         } // case
      } // switch

      pme->m_wTempo = 100;
      pme->m_wTune = 0x40;
      pme->m_wSilenceTimeMS = OEMMM_RINGER_SILENCE_TIME; // one second wait between rings.
      pme->m_dwTickTimeMS = OEMMM_TICK_PERIOD;

      // Initialize callbacks.
      CMediaMMLayer_InitCallback((IMedia *)pme, MM_CMD_PLAY, pme->m_cbPlay, OEMMM_MAX_CB_PLAY, CMediaBg_CallbackNotify);
      CMediaMMLayer_InitCallback((IMedia *)pme, 0, &pme->m_cbCommand, 1, CMediaBg_CallbackNotify);
      CMediaMMLayer_InitCallback((IMedia *)pme, MM_CMD_GETTOTALTIME, &pme->m_cbGetTotalTime, 1, CMediaBg_CallbackNotify);

      {
         CMXInfo   ci;

         
         ci.nType = OEMCMX_TYPE_AUDFMT_PLAY;
         ci.ph = NULL;
         ci.dwCB = (uint32)(int32)CMediaMMLayer_StatusCB;
         ci.dwUserData = (uint32)pme->m_hObject;
         OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_STATUS_CB, (int32)&ci, 0, 0);
      }

   } // if

   return nRet;

Done:
   // Free anything that could have been successfully allocated before "goto Done"
   if (pme->m_pISound)
   {
      ISOUND_RegisterNotify(pme->m_pISound, NULL, (void *)pme->m_hObject);
      ISOUND_Release(pme->m_pISound);
   }

   FREE(pme);
   return ENOMEMORY;
}

/*==================================================================
   Destructor
==================================================================*/
static void CMediaBg_Delete(CMediaBg * pme)
{
   int i;
   CMediaMMLayer_CancelCallback(pme->m_cbPlay, OEMMM_MAX_CB_PLAY);
   CMediaMMLayer_CancelCallback(&pme->m_cbCommand, 1);
   CMediaMMLayer_CancelCallback(&pme->m_cbGetTotalTime, 1);

   MM_RELEASEIF(pme->m_pISound);

#if defined(FEATURE_WEBAUDIO)
   if (pme->m_clsSelf == AEECLSID_MEDIAPMD && pme->u.m_pPMD)
   {
      TextBlock * pBlock, * pOldBlock;
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbPMDImage);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbJPEGBufAlloc);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbJPEGBufFree);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbClientBufAlloc);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbClientBufFree);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbPicInitialize);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbPicRelease);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbPMDText);      
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbCopyBMPBuf);      
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbCopyPNGBuf);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbCopyJPEGBuf);     
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbSAFCreateDIB);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbSAFLoadFrame);

      if(pme->u.m_pPMD->m_pISafBitmap) {
         if(pme->u.m_pPMD->m_pISafBitmap->cntRGB)
            FREEIF(pme->u.m_pPMD->m_pISafBitmap->pRGB);
         IDIB_Release(pme->u.m_pPMD->m_pISafBitmap);
         pme->u.m_pPMD->m_pISafBitmap = NULL;
      }

      if(pme->u.m_pPMD->m_pIPicBitmap) {
         if(pme->u.m_pPMD->m_pIPicBitmap->cntRGB)
            FREEIF(pme->u.m_pPMD->m_pIPicBitmap->pRGB);
         IDIB_Release(pme->u.m_pPMD->m_pIPicBitmap);
         pme->u.m_pPMD->m_pIPicBitmap = NULL;
      }

      pme->u.m_pPMD->m_pIDispBitmap = NULL;

      MM_RELEASEIF(pme->u.m_pPMD->m_pIDisp);
      pBlock = pme->u.m_pPMD->m_textBlock;
      while (pBlock)
      {
         FREEIF(pBlock->m_text);
         pOldBlock = pBlock;
         pBlock = pOldBlock->m_next;
         FREEIF(pOldBlock);
      }
      pme->u.m_pPMD->m_textBlock = NULL;

      FreeJPEGBuf(pme);
      
      pme->u.m_pPMD->m_displayedPic = FALSE;
      pme->u.m_pPMD->m_displayedSaf = FALSE;
      pme->u.m_pPMD->m_transparency = FALSE;
      
      for (i=0; i<pme->u.m_pPMD->m_numClientBufs; ++i)
         FREEIF(pme->u.m_pPMD->m_ppClientBufs[i]);
      FREEIF(pme->u.m_pPMD->m_ppClientBufs);
   }

#endif // defined(FEATURE_WEBAUDIO)

   FREEIF(pme->u.m_pMP3); // Note: This is a union member!
/*
   switch (pme->m_clsSelf)
   {
      case AEECLSID_MEDIAMP3:
         FREEIF(pme->u.m_pMP3);
         break;

      case AEECLSID_MEDIAQCP:
         FREEIF(pme->u.m_pQCP);
         break;

      case AEECLSID_MEDIAPMD:
#if defined(FEATURE_WEBAUDIO)
         FREEIF(pme->u.m_pPMD);
#endif // defined(FEATURE_WEBAUDIO)
         break;
   } // switch
*/

   pme->m_bStreaming = FALSE;

   if (pme->m_pqCMXDataReq)
   {
      CQueue_Delete(pme->m_pqCMXDataReq);
      pme->m_pqCMXDataReq = NULL;
   }

   if (pme->m_pcbCMXDataReq)
   {
      CALLBACK_Cancel(pme->m_pcbCMXDataReq);
      FREE(pme->m_pcbCMXDataReq);
      pme->m_pcbCMXDataReq = NULL;
   }

   if (pme->m_pqMediaData)
   {
      CQueue_Delete(pme->m_pqMediaData);
      pme->m_pqMediaData = NULL;
   }

   if (pme->m_pcbMediaData)
   {
      CALLBACK_Cancel(pme->m_pcbMediaData);
      FREE(pme->m_pcbMediaData);
      pme->m_pcbMediaData = NULL;
   }

   FREEIF(pme->m_pSrcBuffer);
}

/*==================================================================

==================================================================*/
static int CMediaBg_Play(IMedia * po)
{
   CMediaBg *  pme = (CMediaBg *)po;
   int         nRet;

   nRet = AEEMedia_Play(po);
   if (nRet != SUCCESS)
      return nRet;

   return CMediaMMLayer_Play(pme);;
}

/*==================================================================

==================================================================*/
static int CMediaBg_Stop(IMedia * po)
{
   CMediaBg * pme = (CMediaBg *)po;
   int            nRet;

   nRet = AEEMedia_Stop(po);
   if (nRet != SUCCESS)
      return nRet;

   return CMediaMMLayer_Stop(pme);
}

/*==================================================================

==================================================================*/
static int CMediaBg_Seek(IMedia * po, AEEMediaSeek eSeek, int32 lTimeMS)
{
   CMediaBg * pme = (CMediaBg *)po;
   int            nRet;

   nRet = AEEMedia_Seek(po, eSeek, lTimeMS);
   if (nRet != SUCCESS)
      return nRet;

   if (AEEMedia_IsSeekFrame(eSeek))
      return EUNSUPPORTED;

   return CMediaMMLayer_Seek(pme, eSeek, lTimeMS);
}

/*==================================================================

==================================================================*/
static int CMediaBg_Pause(IMedia * po)
{
   CMediaBg * pme = (CMediaBg *)po;
   int            nRet;

   nRet = AEEMedia_Pause(po);
   if (nRet != SUCCESS)
      return nRet;

   return CMediaMMLayer_Pause(pme);
}

/*==================================================================

==================================================================*/
static int CMediaBg_Resume(IMedia * po)
{
   CMediaBg * pme = (CMediaBg *)po;
   int            nRet;

   nRet = AEEMedia_Resume(po);
   if (nRet != SUCCESS)
      return nRet;

   return CMediaMMLayer_Resume(pme);
}

/*==================================================================

==================================================================*/
static int CMediaBg_GetTotalTime(IMedia * po)
{
   CMediaBg * pme = (CMediaBg *)po;
   int            nRet;

   nRet = AEEMedia_GetTotalTime(po);
   if (nRet != SUCCESS)
      return nRet;

   return CMediaMMLayer_GetTotalTime(pme);
}

/*==================================================================

   Callback Notification Functions: called in BREW context after
   ISHELL_Resume()

==================================================================*/
/*==================================================================
   Media callback notify -- called in BREW context
==================================================================*/
static void CMediaBg_CallbackNotify(void * pUser)
{
   AEEMediaCallback *   pcb = (AEEMediaCallback *)pUser;
   CMediaBg *           pme;

   if (!pcb)
      return;

   pme = (CMediaBg *)pcb->cmdNotify.pIMedia;
   if (!pme)
      return;

   if (pcb->cmdNotify.nStatus == MM_STATUS_MEDIA_SPEC)
   {
      // If it is media spec, then send m_spec to user.
      if (pme->m_clsSelf == AEECLSID_MEDIAMP3 && pme->u.m_pMP3)
      {
         pcb->cmdNotify.pCmdData = (void *)&pme->u.m_pMP3->m_spec;
         pcb->cmdNotify.dwSize = sizeof(AEEMediaMP3Spec);
      }
      else if (pme->m_clsSelf == AEECLSID_MEDIAADPCM && pme->u.m_pADPCM)
      {
         pcb->cmdNotify.pCmdData = (void *)&pme->u.m_pADPCM->m_spec;
         pcb->cmdNotify.dwSize = sizeof(AEEMediaWaveSpec);
      }
   }
   else if (pcb->cmdNotify.nStatus == MM_STATUS_DONE || pcb->cmdNotify.nStatus == MM_STATUS_ABORT)
   {
      if (OEMMM_ISSTREAMING(pme))
         CMediaMMLayer_FlushStreamData(pme);
   }

   CMediaCMX_CallbackNotify(pcb);
}

/*==================================================================
   
==================================================================*/
static int CMediaBg_SetMediaParm(IMedia * po, int nParmID, int32 p1, int32 p2)
{
   CMediaBg *  pme = (CMediaBg *)po;
   int         nRet = EUNSUPPORTED;
   boolean     bProc = TRUE;

   if (pme->m_clsSelf == AEECLSID_MEDIAQCP && pme->u.m_pQCP)
   {
      switch (nParmID)
      {
         case MM_PARM_TICK_TIME:
            pme->m_dwTickTimeMS = (int)p1;
            nRet = SUCCESS;
            break;

         case MM_PARM_QCP_FORMAT:
            pme->u.m_pQCP->m_nFormat = (byte)p1;
            nRet = SUCCESS;
            break;

         default:
            bProc = FALSE;
            break;
      }

      if (bProc)
         return nRet;
   }

   bProc = TRUE;
   switch (nParmID)
   {
      case MM_PARM_VOLUME:
      {
         nRet = CMediaMMLayer_SetVolume(pme, (uint16)p1);
         if (!nRet || nRet == MM_PENDING)
            pme->m_wVolume = (uint16)p1;
         break;
      }

      case MM_PARM_AUDIO_DEVICE:
         nRet = CMediaMMLayer_SetDevice(pme, (AEESoundDevice)p1);
         break;

      case MM_PARM_AUDIO_PATH:
         nRet = CMediaMMLayer_SetPath(pme, (int)p1);
         break;

      case MM_PARM_MUTE:
         nRet = CMediaMMLayer_SetMute(pme, (boolean)p1);
         break;

      case MM_PARM_TEMPO:
         if (AEEMedia_IsPlayOn(pme))
            nRet = CMediaMMLayer_SetTempo(pme, (uint32)p1);
         else
            nRet = EBADSTATE;
         break;

      case MM_PARM_TUNE:
         if (AEEMedia_IsPlayOn(pme))
            nRet = CMediaMMLayer_SetTune(pme, (uint32)p1);
         else
            nRet = EBADSTATE;
         break;

      case MM_PARM_PAN:
         if (AEEMedia_IsPlayOn(pme))
            nRet = CMediaMMLayer_SetPan(pme, (uint32)p1);
         else
            nRet = EBADSTATE;
         break;

      case MM_PARM_POS:
      case MM_PARM_PLAY_REPEAT:
      case MM_PARM_TICK_TIME:
         break;

      case MM_PARM_RECT:
#if defined(FEATURE_WEBAUDIO)
         if (pme->m_clsSelf == AEECLSID_MEDIAPMD && pme->u.m_pPMD)
         {
            if (p1)
               MEMCPY(&pme->u.m_pPMD->m_rectImage, (void *)p1, sizeof(AEERect));
            if (p2)
               MEMCPY(&pme->u.m_pPMD->m_rectText, (void *)p2, sizeof(AEERect));
            nRet = SUCCESS;
         }
#endif // defined(FEATURE_WEBAUDIO)
         break;

      case MM_PARM_CHANNEL_SHARE:
      {
           boolean  bEnable = (boolean)p1;
           if (pme->m_bChannelShare != bEnable)
           {
             pme->m_bChannelShare = bEnable;
           }
           nRet = SUCCESS;
      }
      break;

      default:
         bProc = FALSE;
         break;
   }

   if (bProc)
      return nRet;

   return CMediaCMX_SetMediaParm(po, nParmID, p1, p2);
}

/*==================================================================
   
==================================================================*/
static int CMediaBg_GetMediaParm(IMedia * po, int nParmID, int32 * pP1, int32 * pP2)
{
   CMediaBg *  pme = (CMediaBg *)po;
   int         nRet = SUCCESS;
   boolean     bProc = TRUE;

   if (pme->m_clsSelf == AEECLSID_MEDIAQCP && pme->u.m_pQCP)
   {
      switch (nParmID)
      {
         case MM_PARM_TICK_TIME:
            if (pP1)
               *pP1 = pme->m_dwTickTimeMS;
            break;

         case MM_PARM_QCP_FORMAT:
            if (pP1)
               *pP1 = pme->u.m_pQCP->m_nFormat;
            break;

         default:
            bProc = FALSE;
            break;
      }

      if (bProc)
         return nRet;
   }

   bProc = TRUE;
   switch (nParmID)
   {
      case MM_PARM_VOLUME:
         // No need to alloc a command callback since the callback from
         // ISound uniquely identifies this operation and also
         // gets called in BREW context.
         ISOUND_GetVolume(pme->m_pISound);
         nRet = MM_PENDING;
         break;

      case MM_PARM_AUDIO_DEVICE:
         nRet = EUNSUPPORTED;
         break;

      case MM_PARM_AUDIO_PATH:
         if (pP1)
            *pP1 = (int32)pme->m_nAPath;
         break;

      case MM_PARM_MUTE:
         if (pP1)
            *pP1 = (int32)pme->m_bMute;
         break;

      case MM_PARM_TEMPO:
         if (pP1)
            *pP1 = (int32)pme->m_wTempo;
         break;

      case MM_PARM_TUNE:
         if (pP1)
            *pP1 = (int32)pme->m_wTune;
         break;

      case MM_PARM_PAN:
         if (pP1)
            *pP1 = (int32)pme->m_wPan;
         break;

      case MM_PARM_POS:
      case MM_PARM_PLAY_REPEAT:
      case MM_PARM_TICK_TIME:
         nRet = EUNSUPPORTED;
         break;

      case MM_PARM_RECT:
#if defined(FEATURE_WEBAUDIO)
         if (pme->m_clsSelf == AEECLSID_MEDIAPMD && pme->u.m_pPMD)
         {
            if (pP1)
               MEMCPY(pP1, &pme->u.m_pPMD->m_rectImage, sizeof(AEERect));
            if (pP2)
               MEMCPY(pP2, &pme->u.m_pPMD->m_rectText, sizeof(AEERect));
         }
         else
#endif // defined(FEATURE_WEBAUDIO)
            nRet = EUNSUPPORTED;
         break;

      default:
         bProc = FALSE;
         break;
   }

   if (bProc)
      return nRet;

   return CMediaCMX_GetMediaParm(po, nParmID, pP1, pP2);
}

/*==================================================================
   
==================================================================*/
static int CMediaBg_Record(IMedia * po)
{
   CMediaBg * pme = (CMediaBg *)po;
   int         nRet;

   if (pme->m_clsSelf == AEECLSID_MEDIAQCP && pme->u.m_pQCP)
   {
      nRet = AEEMedia_Record(po);
      if (nRet == SUCCESS)
         nRet = CMediaMMLayer_QCPRecord(pme, pme->u.m_pQCP->m_nFormat);
   }
   else if (pme->m_clsSelf == AEECLSID_MEDIAPCM || (pme->m_clsSelf == AEECLSID_MEDIAADPCM && pme->u.m_pADPCM))
   {
      nRet = AEEMedia_Record(po);
      if (nRet == SUCCESS)
         nRet = CMediaMMLayer_PCMRecord(pme);
   }
   else
      nRet = EUNSUPPORTED;

   return nRet;
}

/*===============================================================================

                     IMediaMIDIOutMsg PUBLIC FUNCTION DEFINITIONS

===============================================================================*/
/*==================================================================
   Called only once during AEE initialization
==================================================================*/
void IMediaMIDIOutMsg_Init(IShell * ps)
{
   // Initialize ASIC MM Layer here...
   CMediaMMLayer_Init(ps);
}

/*==================================================================
   IMediaMIDIOutMsg constructor
   Called by ISHELL_CreateInstance()
==================================================================*/
int IMediaMIDIOutMsg_New(IShell * ps, AEECLSID cls, void **ppif)
{
   IBase * pobj = CMediaCMX_New(ps, cls, &gMediaMIDIOutMsgFuncs, sizeof(CMediaMIDIOutMsg), (PFNNOTIFY)CMediaMIDIOutMsg_Delete);

   if (pobj)
   {
      CMediaMIDIOutMsg *  pme = (CMediaMIDIOutMsg *)pobj;
      CMediaMMLayer_InitCallback((IMedia *)pme, MM_CMD_PLAY, pme->m_cbPlay, OEMMM_MAX_CB_PLAY, CMediaBg_CallbackNotify);
      CMediaMMLayer_InitCallback((IMedia *)pme, 0, &pme->m_cbCommand, 1, CMediaBg_CallbackNotify);
   }

   *ppif = pobj;
   return pobj ? SUCCESS : ENOMEMORY;
}


/*==================================================================
   Destructor   
==================================================================*/
static void CMediaMIDIOutMsg_Delete(CMediaMIDIOutMsg * pme)
{
   CMediaMMLayer_CancelCallback(pme->m_cbPlay, OEMMM_MAX_CB_PLAY);
   CMediaMMLayer_CancelCallback(&pme->m_cbCommand, 1);

#if defined(FEATURE_MIDI_OUT)
   if (pme->m_bMIDIOut)
   {
      CMIDIOut_Release();
      pme->m_bMIDIOut = FALSE;
   }
#endif // defined(FEATURE_MIDI_OUT)
}

/*==================================================================
   
==================================================================*/
static int CMediaMIDIOutMsg_SetMediaParm(IMedia * po, int nParmID, int32 p1, int32 p2)
{
   CMediaMIDIOutMsg *   pme = (CMediaMIDIOutMsg *)po;
   int                  nRet = EUNSUPPORTED;
   boolean              bProc = TRUE;

   // Volume/Path/Mute/Pan is handled.
   switch (nParmID)
   {
      case MM_PARM_VOLUME:
      case MM_PARM_PAN:
      case MM_PARM_TEMPO:
      case MM_PARM_TUNE:
         break;

      case MM_PARM_MIDI_MSG:
         if (!p1 || !p2)
            nRet = EBADPARM;
         else
         {
            pme->m_pMsg = (uint8 *)p1;
            pme->m_dwSize = (uint32)p2;
            nRet = SUCCESS;
         }
         break;

      default:
         bProc = FALSE;
         break;
   }

   if (bProc)
      return nRet;

   return CMediaCMX_SetMediaParm(po, nParmID, p1, p2);
}

/*==================================================================
   
==================================================================*/
static int CMediaMIDIOutMsg_GetMediaParm(IMedia * po, int nParmID, int32 * pP1, int32 * pP2)
{
   CMediaMIDIOutMsg *   pme = (CMediaMIDIOutMsg *)po;
   int                  nRet = SUCCESS;
   boolean              bProc = TRUE;

   // Volume/Path/Mute/Pan is handled.
   switch (nParmID)
   {
      case MM_PARM_VOLUME:
      case MM_PARM_PAN:
      case MM_PARM_TEMPO:
      case MM_PARM_TUNE:
         nRet = EUNSUPPORTED;
         break;

      case MM_PARM_MIDI_MSG:
         if (pP1)
            *pP1 = (int32)pme->m_pMsg;
         if (pP2)
            *pP2 = (int32)pme->m_dwSize;
         break;

      default:
         bProc = FALSE;
         break;
   }

   if (bProc)
      return nRet;

   return CMediaCMX_GetMediaParm(po, nParmID, pP1, pP2);
}

/*==================================================================
   
==================================================================*/
static int CMediaMIDIOutMsg_Play(IMedia * po)
{
   CMediaMIDIOutMsg *  pme = (CMediaMIDIOutMsg *)po;
   int            nRet;

   nRet = AEEMedia_Play(po);
   if (nRet != SUCCESS)
      return nRet;

   return CMediaMMLayer_MIDIOutMsg(pme);
}

/*===============================================================================

                     IMediaMIDIOutQCP PUBLIC FUNCTION DEFINITIONS

===============================================================================*/
/*==================================================================
   Called only once during AEE initialization
==================================================================*/
void IMediaMIDIOutQCP_Init(IShell * ps)
{
   if (gInitFlags.gbMediaMIDIOutQCPInit)
      return;

   gInitFlags.gbMediaMIDIOutQCPInit = TRUE;

   // If found in the mod table, then register the formats...

   if (CMediaFormat_InModTable(AEECLSID_MEDIAMIDIOUTQCP, (void *)IMediaMIDIOutQCP_Init, NULL))
      AEEMedia_Init(ps, MT_AUDIO_QCF, AEECLSID_MEDIAMIDIOUTQCP);

   // Initialize ASIC MM Layer here...
   CMediaMMLayer_Init(ps);
}

/*==================================================================
   IMediaMIDIOutQCP constructor
   Called by ISHELL_CreateInstance()
==================================================================*/
int IMediaMIDIOutQCP_New(IShell * ps, AEECLSID cls, void **ppif)
{
   IBase * pobj = CMediaCMX_New(ps, cls, &gMediaMIDIOutQCPFuncs, sizeof(CMediaMIDIOutQCP), (PFNNOTIFY)CMediaMIDIOutQCP_Delete);

   if (pobj)
   {

      CMediaMIDIOutQCP *  pme = (CMediaMIDIOutQCP *)pobj;
      CMediaMMLayer_InitCallback((IMedia *)pme, MM_CMD_PLAY, pme->m_cbPlay, OEMMM_MAX_CB_PLAY, CMediaBg_CallbackNotify);
      CMediaMMLayer_InitCallback((IMedia *)pme, 0, &pme->m_cbCommand, 1, CMediaBg_CallbackNotify);

#if defined(FEATURE_MIDI_OUT_QCP)
      {
         CMXInfo  ci;
         ci.nType = OEMCMX_TYPE_MIDI_OUT_QCP;
         ci.ph = NULL;
         ci.dwCB = (uint32)(int32)CMediaMMLayer_MIDIOutQCPStatusCB;
         ci.dwUserData = (uint32)pme->m_hObject;
         OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_STATUS_CB, (int32)&ci, 0, 0);
      }
#endif // defined(FEATURE_MIDI_OUT_QCP)
   }

   *ppif = pobj;
   return pobj ? SUCCESS : ENOMEMORY;
}

/*==================================================================
   Destructor   
==================================================================*/
static void CMediaMIDIOutQCP_Delete(CMediaMIDIOutQCP * pme)
{
   CMediaMMLayer_CancelCallback(pme->m_cbPlay, OEMMM_MAX_CB_PLAY);
   CMediaMMLayer_CancelCallback(&pme->m_cbCommand, 1);

#if defined(FEATURE_MIDI_OUT)
   if (pme->m_bMIDIOut)
   {
      CMIDIOut_Release();
      pme->m_bMIDIOut = FALSE;
   }
#endif // defined(FEATURE_MIDI_OUT)
}

/*==================================================================
   
==================================================================*/
static int CMediaMIDIOutQCP_SetMediaParm(IMedia * po, int nParmID, int32 p1, int32 p2)
{
   CMediaMIDIOutQCP * pme = (CMediaMIDIOutQCP *)po;
   int         nRet = EUNSUPPORTED;
   boolean     bProc = TRUE;

   // Volume/Path/Mute/Pan is handled.
   switch (nParmID)
   {
      case MM_PARM_VOLUME:
         nRet = CMediaMMLayer_MIDIOutQCPSetVolume(pme, pme->m_dwChannel, (uint16)p1);
         break;

      case MM_PARM_PAN:
         nRet = CMediaMMLayer_MIDIOutQCPSetPan(pme, pme->m_dwChannel, (uint16)p1);
         break;

      case MM_PARM_TEMPO:
      case MM_PARM_TUNE:
         break;

      default:
         bProc = FALSE;
         break;
   }

   if (bProc)
      return nRet;

   return CMediaCMX_SetMediaParm(po, nParmID, p1, p2);
}

/*==================================================================
   
==================================================================*/
static int CMediaMIDIOutQCP_GetMediaParm(IMedia * po, int nParmID, int32 * pP1, int32 * pP2)
{
   int                  nRet = SUCCESS;
   boolean              bProc = TRUE;

   // Tempo/Mute/Pan is handled.
   switch (nParmID)
   {
      case MM_PARM_TEMPO:
      case MM_PARM_TUNE:
      case MM_PARM_PAN:
         nRet = EUNSUPPORTED;
         break;

      default:
         bProc = FALSE;
         break;
   }

   if (bProc)
      return nRet;

   return CMediaCMX_GetMediaParm(po, nParmID, pP1, pP2);
}

/*==================================================================
   
==================================================================*/
static int CMediaMIDIOutQCP_Play(IMedia * po)
{
   CMediaMIDIOutQCP *   pme = (CMediaMIDIOutQCP *)po;
   int                  nRet;

   nRet = AEEMedia_Play(po);
   if (nRet != SUCCESS)
      return nRet;

   return CMediaMMLayer_MIDIOutQCPPlay(pme);
}

/*==================================================================
   
==================================================================*/
static int CMediaMIDIOutQCP_Stop(IMedia * po)
{
   CMediaMIDIOutQCP *   pme = (CMediaMIDIOutQCP *)po;
   int                  nRet;

   nRet = AEEMedia_Stop(po);
   if (nRet != SUCCESS)
      return nRet;

   return CMediaMMLayer_MIDIOutQCPStop(pme, pme->m_dwChannel);
}

/*==================================================================
   
==================================================================*/
static int CMediaMIDIOutQCP_Pause(IMedia * po)
{
   CMediaMIDIOutQCP *   pme = (CMediaMIDIOutQCP *)po;
   int                  nRet;

   nRet = AEEMedia_Pause(po);
   if (nRet != SUCCESS)
      return nRet;

   return CMediaMMLayer_MIDIOutQCPPause(pme);
}

/*==================================================================
   
==================================================================*/
static int CMediaMIDIOutQCP_Resume(IMedia * po)
{
   CMediaMIDIOutQCP *   pme = (CMediaMIDIOutQCP *)po;
   int                  nRet;

   nRet = AEEMedia_Resume(po);
   if (nRet != SUCCESS)
      return nRet;

   return CMediaMMLayer_MIDIOutQCPResume(pme);
}

/*==================================================================

   CMediaBg Local Functions

==================================================================*/

/*==================================================================

   CMediaCMX: MMLayer

==================================================================*/
/*===========================================================================

                      MACRO DEFINITIONS

===========================================================================*/
#define CMediaMMLayer_ISCOMMAND(pcb)     ((pcb)->bInUse)
#define CMediaMMLayer_ISCMDPLAYREC(pcb)  ((pcb)->cmdNotify.nCmd == MM_CMD_PLAY \
                                          || (pcb)->cmdNotify.nCmd == MM_CMD_RECORD)
#define CMediaMMLayer_ISPLAYCTL(pcb)     (CMediaMMLayer_ISCOMMAND(pcb) && CMediaMMLayer_ISCMDPLAYREC(pcb) \
                                          && (pcb)->cmdNotify.nSubCmd > OEMMM_CTLCMD_NONE)
#define CMediaMMLayer_ISSEEK(pcb)        (CMediaMMLayer_ISCOMMAND(pcb) && CMediaMMLayer_ISCMDPLAYREC(pcb) \
                                          && (pcb)->cmdNotify.nSubCmd == OEMMM_CTLCMD_SEEK)
#define CMediaMMLayer_ISPAUSE(pcb)       (CMediaMMLayer_ISCOMMAND(pcb) && CMediaMMLayer_ISCMDPLAYREC(pcb) \
                                          && (pcb)->cmdNotify.nSubCmd == OEMMM_CTLCMD_PAUSE)
#define CMediaMMLayer_ISRESUME(pcb)      (CMediaMMLayer_ISCOMMAND(pcb) && CMediaMMLayer_ISCMDPLAYREC(pcb) \
                                          && (pcb)->cmdNotify.nSubCmd == OEMMM_CTLCMD_RESUME)
#define CMediaMMLayer_ISSTOP(pcb)        (CMediaMMLayer_ISCOMMAND(pcb) && CMediaMMLayer_ISCMDPLAYREC(pcb) \
                                          && (pcb)->cmdNotify.nSubCmd == OEMMM_CTLCMD_STOP)

/*===========================================================================

                      PUBLIC DATA DECLARATIONS

===========================================================================*/
typedef enum OEMMMStatus
{
   OEMMM_STATUS_PLAY = 0,
   OEMMM_STATUS_PLAYCTL,
   OEMMM_STATUS_SETPARM
} OEMMMStatus;

typedef enum OEMMMCtlCmd
{
   OEMMM_CTLCMD_NONE = 0,
   OEMMM_CTLCMD_SEEK,
   OEMMM_CTLCMD_PAUSE,
   OEMMM_CTLCMD_RESUME,
   OEMMM_CTLCMD_STOP
} OEMMMCtlCmd;

static int CMediaMMLayer_SetSource(CMediaCMX * pme, cmx_handle_type * ph);

#if defined(FEATURE_WEBAUDIO)
static int CMediaMMLayer_NewDisplay(CMediaBg * pme, IDisplay ** ppDisp);
#endif
static void CMediaMMLayer_RequestSourceData(CMediaBg * pme);
static void CMediaMMLayer_ReadSource(CMediaBg * pme);

#if defined(FEATURE_WEBAUDIO)
//SAF constants. These should have been in cmx.h, but are
//in cmx.c instead! Watch for these and fix them if any 
//newer version CMX changes the SAF image bounds.
#define OEMMM_SAF_FRAME_MAXWIDTH   160
#define OEMMM_SAF_FRAME_MAXHEIGHT  160

#define OEMMM_PAL_ALL_WHITE  0x00ffffff

#define  OEMMM_JUST_LEFT     0x01
#define  OEMMM_JUST_RIGHT    0x02
#define  OEMMM_JUST_XCENTR   0x03
#define  OEMMM_JUST_XPCENT   0x04
#define  OEMMM_JUST_XPIXEL   0x05
#define  OEMMM_JUST_TOP      0x10
#define  OEMMM_JUST_BOT      0x20
#define  OEMMM_JUST_YCENTR   0x30
#define  OEMMM_JUST_YPCENT   0x40
#define  OEMMM_JUST_YPIXEL   0x50

typedef struct WinBMPInfoHdr_s {
   dword dwBIHdrSize;
   dword dwBIWidth;
   int32 dwBIHeight;
   word  wNumPlanes;
   word  wBitsPerPix;
   dword dwBICompress;
   dword dwBISize;
   dword dwBIXPixPerMtr;
   dword dwBIYPixPerMtr;
   dword dwBIClrUsed;
   dword dwBIClrImp;
} WinBMPInfoHdr;

static void     SetImageOffsets(CMediaBg * pme);
static void     SetSAFOffsets(CMediaBg * pme);
#endif // defined(FEATURE_WEBAUDIO)

#if defined(FEATURE_WEBAUDIO)
/*===============================================================================

      DownShift Stuff

===============================================================================*/
typedef struct AEEDShift_s {
   uint8   nSrcDepth;
   uint8   nDstDepth;
   uint16  nWidth;
   uint16  nSrcPitch;
   uint16  nDstPitch;
   uint16  nLayout;  //No. of pixels for R, G, B (for eg. 332, for 3R, 3G, 2B)
   boolean bDither;
   byte    Reserved;
} AEEDShift;

#define DITHERING_ON
typedef struct _DitherCoef {
   int16 coRed;
   int16 coGreen;
   int16 coBlue;
   int16 reserved;
} DitherCoef;

typedef struct IDShift  IDShift;
typedef int    (*PFNDSHIFT)(IDShift *pMe, byte *pDst, byte *pSrc, boolean bSwap);

struct IDShift
{
   IShell         *piShell;

   unsigned short  usRef;

   AEEDShift       dshiftInfo;
   DitherCoef *    pDitherBuf;
   PFNDSHIFT       pfnDShift;
};

static int    IDShift_New(IShell *piShell, void **pp);
static void   IDShift_Delete(IDShift *pMe);
static int    IDShift_Configure(IDShift *pMe, AEEDShift *pShiftInfo);
static int    IDShift_Convert(IDShift *pMe, byte *pDst, byte *pSrc, boolean bSwap);

static int    DShiftTo1(IDShift *pMe, byte *pDst, byte *pSrc, boolean bSwap);
static int    DShiftTo4(IDShift *pMe, byte *pDst, byte *pSrc, boolean bSwap);
static int    DShiftTo8(IDShift *pMe, byte *pDst, byte *pSrc, boolean bSwap);
static int    DShiftTo16(IDShift *pMe, byte *pDst, byte *pSrc, boolean bSwap);
static byte * DitherSrc(IDShift * pMe, byte *pSrc);

#define DITHER_RGQUANT(x)  ((x)/32 * 32) 
#define DITHER_BQUANT(x)   ((x)/64 * 64) 
 
#define RICOMP(x, y)       (((x) - (y)) * 7 /16)
#define BOCOMP(x, y)       (((x) - (y)) * 5 /16)
#define BLCOMP(x, y)       (((x) - (y)) * 3 /16)
#define BRCOMP(x, y)       (((x) - (y)) * 1 /16)

static int IDShift_New(IShell *piShell, void **pp)
{
   IDShift *pMe;
   *pp = 0;

   
   pMe = MALLOCREC(IDShift);

   if ((IDShift *)0 == pMe) {
      return ENOMEMORY;
   }

   *pp = (void *)pMe;

   pMe->piShell = piShell;
   ISHELL_AddRef(piShell);

   return SUCCESS;
}

static void IDShift_Delete(IDShift *pMe)
{

   ISHELL_Release(pMe->piShell);

   if(pMe->pDitherBuf)
      FREE(pMe->pDitherBuf);

   FREE(pMe);
}

static int    IDShift_Configure(IDShift *pMe, AEEDShift *pShiftInfo) {

   if(!pShiftInfo || pShiftInfo->nSrcDepth != 24)
      return EBADPARM;


   switch(pShiftInfo->nDstDepth) {
      case 1:
         pMe->pfnDShift = (PFNDSHIFT) DShiftTo1;
         break;
      case 4:
         pMe->pfnDShift = (PFNDSHIFT) DShiftTo4;
         break;
      case 8:
         pMe->pfnDShift = (PFNDSHIFT) DShiftTo8;
         break;
      case 16:
         pMe->pfnDShift = (PFNDSHIFT) DShiftTo16;
         break;

      default:
         return EBADPARM;
   }
   if(pShiftInfo->bDither) {
      pMe->pDitherBuf = (DitherCoef *) MALLOC(pShiftInfo->nSrcPitch);
      if(!pMe->pDitherBuf) {
         pMe->pfnDShift = NULL;
         return EFAILED;
      }
   }

   MEMCPY((void *) &pMe->dshiftInfo, (void *) pShiftInfo, sizeof(AEEDShift));

   return SUCCESS;
}

static int    IDShift_Convert(IDShift *pMe, byte *pDst, byte *pSrc, boolean bSwap) {

   if(pMe->pfnDShift) {
      if(pMe->dshiftInfo.bDither)
         pSrc = DitherSrc(pMe, pSrc);
      BRIDLE_ASSERT_SAFE_PFN(pMe->pfnDShift);  
      return(pMe->pfnDShift(pMe, pDst, pSrc, bSwap));
   }
   else
      return EFAILED;
}

static int    DShiftTo1(IDShift *pMe, byte *pDst, byte *pSrc, boolean bSwap) {


   return SUCCESS;
}

static int    DShiftTo4(IDShift *pMe, byte *pDst, byte *pSrc, boolean bSwap) {


   return SUCCESS;
}

static int    DShiftTo8(IDShift *pMe, byte *pDst, byte *pSrc, boolean bSwap) {

   int i;

   if(bSwap) {
      for(i = 0; i < pMe->dshiftInfo.nWidth; i++) {
         *pDst  = ((*pSrc++) & 0xC0) >> 6;
         *pDst |= ((*pSrc++) & 0xE0) >> 3;
         *pDst |= (*pSrc++) & 0xE0;
         pDst++;
      }

   }
   else {
      for(i = 0; i < pMe->dshiftInfo.nWidth; i++) {
         *pDst  = (*pSrc++) & 0xE0;
         *pDst |= ((*pSrc++) & 0xE0) >> 3;
         *pDst |= ((*pSrc++) & 0xC0) >> 6;
         pDst++;
      }
   }

   return SUCCESS;
}

static int    DShiftTo16(IDShift *pMe, byte *pDst, byte *pSrc, boolean bSwap) {

   int i;
   uint16 *pDst16 = (uint16 *) pDst;

   if(!bSwap) {
      for(i = 0; i < pMe->dshiftInfo.nWidth; i++) {
         *pDst16  = ((uint16)((*pSrc++) & 0xF8)) << 8;
         *pDst16 |= ((uint16)((*pSrc++) & 0xFC)) << 3;
         *pDst16 |= ((*pSrc++) & 0xF8) >> 3;
         pDst16++;
      }
   }
   else {
      for(i = 0; i < pMe->dshiftInfo.nWidth; i++) {
         *pDst16  = ((*pSrc++) & 0xF8) >> 3;
         *pDst16 |= ((uint16)((*pSrc++) & 0xFC)) << 3;
         *pDst16 |= ((uint16)((*pSrc++) & 0xF8)) << 8;
         pDst16++;
      }
   }

   return SUCCESS;
}

static byte * DitherSrc(IDShift * pMe, byte *pSrc) {

   uint32 i;
   
   byte * pDitherSrc = pSrc;
   int   compVal, errSrc;
   int brRed = 0;
   int brGreen = 0;
   int brBlue = 0;
   uint32 nPix = pMe->dshiftInfo.nSrcPitch;

   
   if(!pMe->pDitherBuf)
      pMe->pDitherBuf = (DitherCoef *) MALLOC(pMe->dshiftInfo.nWidth * sizeof(DitherCoef));

   if(pMe->pDitherBuf) {
      for(i = 0; i < nPix; i++) {
         //Red First
         errSrc = *pDitherSrc + pMe->pDitherBuf[i].coRed;

         compVal = DITHER_RGQUANT(errSrc);
         *pDitherSrc = MIN(compVal, 255);

         if(brRed != 0)
            pMe->pDitherBuf[i].coRed = brRed;
         
         pMe->pDitherBuf[i].coRed += BOCOMP(errSrc, compVal);


         if( i < (nPix - 1)) {
            pMe->pDitherBuf[i+1].coRed += RICOMP(errSrc, compVal);
            brRed = BRCOMP(errSrc, compVal);
         }
         if (i > 0)
            pMe->pDitherBuf[i-1].coRed += BLCOMP(errSrc, compVal);

         //Then Green
         pDitherSrc++;
         errSrc = *pDitherSrc + pMe->pDitherBuf[i].coGreen;
         
         compVal = DITHER_RGQUANT(errSrc);

         *pDitherSrc = MIN(compVal, 255);

         if(brGreen != 0)
            pMe->pDitherBuf[i].coGreen = brGreen;
         
         pMe->pDitherBuf[i].coGreen += BOCOMP(errSrc, compVal);


         if( i < (nPix - 1)) {
            pMe->pDitherBuf[i+1].coGreen += RICOMP(errSrc, compVal);
            brGreen = BRCOMP(errSrc, compVal);
         }
         if (i > 0)
            pMe->pDitherBuf[i-1].coGreen += BLCOMP(errSrc, compVal);

         //Then blue
         pDitherSrc++;
         errSrc = *pDitherSrc + pMe->pDitherBuf[i].coBlue;

         compVal = DITHER_BQUANT(errSrc);
         *pDitherSrc = MIN(compVal, 255);

         if(brBlue != 0)
            pMe->pDitherBuf[i].coBlue = brBlue;
         
         pMe->pDitherBuf[i].coBlue += BOCOMP(errSrc, compVal);

         if( i < (nPix - 1)) {
            pMe->pDitherBuf[i+1].coBlue += RICOMP(errSrc, compVal);
            brBlue = BRCOMP(errSrc, compVal);
         }
         if (i > 0)
            pMe->pDitherBuf[i-1].coBlue += BLCOMP(errSrc, compVal);
         
         pDitherSrc++;
      }
 
   }

   return pSrc;
}
#endif // defined(FEATURE_WEBAUDIO)

/*==================================================================
   Called only once during AEE initialization.
==================================================================*/
static void CMediaMMLayer_Init(IShell * ps)
{
   if (gInitFlags.gbCMXInit)
      return;

   gInitFlags.gbCMXInit = TRUE;

   CALLBACK_Init(&gcbAEEExit, CMediaMMLayer_AEEExitNotify, NULL);
   ISHELL_OnExit(ps, &gcbAEEExit);
}

/*==================================================================
   Called only once during AEE Exit
==================================================================*/
static void CMediaMMLayer_AEEExitNotify(void * pUser)
{
   gInitFlags.gbCMXInit = FALSE;
   gInitFlags.gbMediaBgInit = FALSE;
   gInitFlags.gbMediaMIDIOutQCPInit = FALSE;
}

/*==================================================================
   Assume: ppDisp is not NULL.
==================================================================*/
#if defined(FEATURE_WEBAUDIO)
static int CMediaMMLayer_NewDisplay(CMediaBg * pme, IDisplay ** ppDisp)
{
   ACONTEXT *  pacLast = AEE_EnterAppContext(pme->m_pac);
   int         nErr = ISHELL_CreateInstance(pme->m_pIShell, AEECLSID_DISPLAY, (void **)ppDisp);
   AEE_LeaveAppContext(pacLast);
   return nErr;
}
#endif //defined(FEATURE_WEBAUDIO)

/*==================================================================

      Sound functions

==================================================================*/
/*==================================================================

==================================================================*/
static int CMediaMMLayer_SetVolume(CMediaBg * pme, uint16 wVolume)
{
   if (!pme->m_pISound)
      return EUNSUPPORTED;

   if (wVolume > 100)
      return EBADPARM;

   ISOUND_SetVolume(pme->m_pISound, wVolume);

   return MM_PENDING;
}

/*==================================================================

==================================================================*/
static int CMediaMMLayer_SetDevice(CMediaBg * pme, AEESoundDevice eDevice)
{
   AEESoundInfo   si;
   ISound *       pISound = pme->m_pISound   ;

   if (!pISound)
      return EUNSUPPORTED;

   ISOUND_Get(pISound, &si);
   si.eDevice = eDevice;
   ISOUND_Set(pISound, &si);

   ISOUND_SetDevice(pISound);

   return MM_PENDING;
}

/*==================================================================
   For playback
==================================================================*/
static int CMediaMMLayer_SetPath(CMediaBg * pme, int nAPath)
{
   if (pme->m_clsSelf != AEECLSID_MEDIAQCP)
      return EUNSUPPORTED;

   if (nAPath > MM_APATH_BOTH)
      return EBADPARM;

   pme->m_nAPath = nAPath;

   return SUCCESS;
}

/*==================================================================

==================================================================*/
static int CMediaMMLayer_SetMute(CMediaBg * pme, boolean bMute)
{
   pme->m_bMute = bMute;

   return CMediaMMLayer_SetVolume(pme, (uint16)(bMute ? 0 : pme->m_wVolume));
}


/*==================================================================

   Callback Notification Functions: called in BREW context after
   AEE_ResumeCallback()

==================================================================*/
/*==================================================================

==================================================================*/
static void CMediaMMLayer_SoundCB(void * pUser, AEESoundCmd eCBType, AEESoundStatus eStatus, uint32 dwParam)
{
   CMediaBg *      pme = (CMediaBg *)AEEObjectMgr_GetObject((AEEObjectHandle)pUser);
   AEESoundCmdData *    pData = (AEESoundCmdData *)dwParam;
   AEEMediaCallback     mcb;

   if (!pme)
      return;
   
   MEMCPY(&mcb, &pme->m_cbCommand, sizeof(AEEMediaCallback));

   mcb.bInUse = TRUE;
   mcb.cmdNotify.nSubCmd = MM_PARM_VOLUME;
   mcb.cmdNotify.nCmd = (eCBType == AEE_SOUND_VOLUME_CB) ? MM_CMD_GETMEDIAPARM : MM_CMD_SETMEDIAPARM;
   mcb.cmdNotify.nStatus = (eStatus == AEE_SOUND_SUCCESS) ? MM_STATUS_DONE : MM_STATUS_ABORT;
   mcb.cmdNotify.pCmdData = NULL;
   mcb.cmdNotify.dwSize = 0;

   if (pData && eCBType == AEE_SOUND_VOLUME_CB && eStatus == AEE_SOUND_SUCCESS)
   {
      mcb.cmdNotify.pCmdData = (void *)(uint32)pData->wVolume;
      mcb.cmdNotify.dwSize = sizeof(pData->wVolume);
   }

   AEEMedia_CallbackNotify((AEEMedia *)pme, &mcb);
}

/*==================================================================

            LOCAL FUNCTIONS

==================================================================*/
static size_t strlcpy(char * dst, const char * src, size_t nSize)
{
   size_t  ret;
   char   *pcDst;

   if( !nSize )
      return STRLEN(src);

   for( ret = 0, pcDst = dst; (ret < nSize) && (*pcDst++ = *src); ret++, src++ ) {}

   if( ret == nSize ) dst[nSize-1] = '\0';

   while( *src++ ) ret++;

   return ret;
}

//$r$
/*==================================================================
   CQueue
==================================================================*/
static int  CQueue_New(int32 nMaxSize, boolean bMutex, CQueue ** ppQ)
{
   CQueue * pme;
   uint8 *  pBuf = MALLOC(sizeof(CQueue) + nMaxSize);
   if (!pBuf)
      return ENOMEMORY;

   pme = (CQueue *)pBuf;
   pme->pBuffer = pBuf + sizeof(CQueue);
   pme->nMaxSize = nMaxSize;
   pme->bMutex = bMutex;

   *ppQ = pme;

   return SUCCESS;
}

static void CQueue_Delete(CQueue * pme)
{
   FREE(pme);
}

static int32 CQueue_Put(CQueue * pme, uint8 * pData, int32 nSize)
{
   int32 nRet;
   
   if (pme->bMutex)
      AEECriticalSection_Enter(&gMediaCMXCriticalSection);

   if (CQueue_IsPutOk(pme, nSize))
   {
      int32   nTillEnd, nWrap;

      if (pme->nWrite + nSize > pme->nMaxSize)
      {
         nTillEnd = pme->nMaxSize - pme->nWrite;
         nWrap = nSize - nTillEnd;
      }
      else
      {
         nTillEnd = nSize;
         nWrap = 0;
      }

      MEMCPY(pme->pBuffer + pme->nWrite, pData, nTillEnd);
      if (nWrap)
      {
         MEMCPY(pme->pBuffer, pData + nTillEnd, nWrap);
         pme->nWrite = nWrap;
      }
      else
      {
         pme->nWrite += nTillEnd;
         if (pme->nWrite >= pme->nMaxSize)
            pme->nWrite = 0;
      }

      pme->nFill += nSize;

      nRet = nSize;
   }
   else
      nRet = 0;

   if (pme->bMutex)
      AEECriticalSection_Leave(&gMediaCMXCriticalSection);

   FARF(QUEUE, ("===Put:F:%ul W:%ul R:%ul S:%ul===", pme->nFill, pme->nWrite, pme->nRead, nRet));
   return nRet;
}

static int32 CQueue_Get(CQueue * pme, uint8 * pData, int32 nSize)
{
   int32   nRet;

   if (pme->bMutex)
      AEECriticalSection_Enter(&gMediaCMXCriticalSection);

   if (pme->nFill)// && pme->nWrite != pme->m_nRead)
   {
      int32   nTillEnd, nWrap;
      int32   nActualSize = MIN(nSize, pme->nFill);

      if (pme->nRead + nActualSize > pme->nMaxSize)
      {
         nTillEnd = pme->nMaxSize - pme->nRead;
         nWrap = nActualSize - nTillEnd;
      }
      else
      {
         nTillEnd = nActualSize;
         nWrap = 0;
      }

      MEMCPY(pData, pme->pBuffer + pme->nRead, nTillEnd);
      if (nWrap)
      {
         MEMCPY(pData + nTillEnd, pme->pBuffer, nWrap);
         pme->nRead = nWrap;
      }
      else
      {
         pme->nRead += nTillEnd;
         if (pme->nRead >= pme->nMaxSize)
            pme->nRead = 0;
      }

      pme->nFill -= nActualSize;

      nRet = nActualSize;
   }
   else
      nRet = 0;

   if (pme->bMutex)
      AEECriticalSection_Leave(&gMediaCMXCriticalSection);

   FARF(QUEUE, ("===Get:F:%ul W:%ul R:%ul S:%ul===", pme->nFill, pme->nWrite, pme->nRead, nRet));
   return nRet;
}

/*==================================================================
   Called in CMX context.
==================================================================*/
void CMediaMMLayer_CMXDataRequestCB(
  uint32 position,
  uint32 max_len,
  cmx_client_req_cb_func_ptr_type data_req_cb,
  const void *server_client_data,
  const void *client_data
)
{
   CMXDataRequest req;
   CMediaBg *  pme = (CMediaBg *)AEEObjectMgr_GetObject((AEEObjectHandle)client_data);

   FARF(SOURCE, ("===CMXCB: pos=%ul len=%ul", position, max_len));

   if (!pme)
   {
      data_req_cb(CMX_SUCCESS, NULL, 0, server_client_data);   
      return;
   }

   if (!max_len || !data_req_cb)
      return;

   BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme, sizeof(CMediaBg));
   BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme->m_pqCMXDataReq, sizeof(CQueue));
   BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, CQueue_GetBuffer(pme->m_pqCMXDataReq), CQueue_MaxSize(pme->m_pqCMXDataReq));
   BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme->m_pcbCMXDataReq, sizeof(AEECallback));

   // Save the request in queue...
   req.dwPos = position;
   req.dwMaxLen = max_len;
   req.pfnDataReq = data_req_cb;
   req.pServerUserData = (void *)server_client_data;
//   req.pClientUserData = (void *)client_data;

   if (CQueue_Put(pme->m_pqCMXDataReq, (uint8 *)&req, sizeof(req)))
      AEE_ResumeCallback(pme->m_pcbCMXDataReq, pme->m_pac);
}

/*==================================================================

==================================================================*/
static void CMediaMMLayer_CMXDataReqNotify(CMediaBg * pme)
{
   int            nRead;
   int            nLen;
   CMXDataRequest req;

   if (!pme)
      return;

   while (1)
   {
      if (CQueue_IsEmpty(pme->m_pqCMXDataReq))
         break;

      if (CQueue_IsEmpty(pme->m_pqMediaData))
      {
         FARF(SOURCE, ("===CMXNotify: No media data"));
         CMediaMMLayer_RequestSourceData(pme);
         break;
      }

      if (!CQueue_Get(pme->m_pqCMXDataReq, (uint8 *)&req, sizeof(req)))
      {
         FARF(SOURCE, ("===CMXNotify: Fatal mdq error"));
         break;
      }

      CMediaMMLayer_RequestSourceData(pme);

      nLen = MIN(pme->m_dwSrcBufferSize, req.dwMaxLen);
      nRead = CQueue_Get(pme->m_pqMediaData, pme->m_pSrcBuffer, nLen);

      // This check is not required as it is done when callback arrives.
      //   if (!req.pfnDataReq || !req.dwMaxLen)
      //      return;
      FARF(SOURCE, ("===CMXNotify: nReq=%ul nLen=%d", req.dwMaxLen, nLen));

      // $br: BRIDLEize this call.
      OEMMEDIA_SetParm(pme->m_hMediaCMX, OEMCMX_ID_DATA_RESPONSE, (int32)&req, (int32)pme->m_pSrcBuffer, (int32)nRead);
//      req.pfnDataReq(CMX_SUCCESS, pme->m_pSrcBuffer, (uint32)nRead, req.pServerUserData);
   }
}

/*==================================================================

==================================================================*/
static void CMediaMMLayer_RequestSourceData(CMediaBg * pme)
{
   if (pme->m_bEOS || CALLBACK_IsQueued(pme->m_pcbMediaData))
      return;

   if (CQueue_Size(pme->m_pqMediaData) <= (CQueue_MaxSize(pme->m_pqMediaData) / 2))
   {
      FARF(SOURCE, ("===ReqSrc: MORE data"));
      AEE_ResumeCallback(pme->m_pcbMediaData, pme->m_pac);
   }
}

/*==================================================================

==================================================================*/
static void CMediaMMLayer_ReadSource(CMediaBg * pme)
{
   ISource *   pSource = (ISource *)pme->m_md.pData;
   int32       nRead;
   boolean     bNotify = FALSE;

   while (1)
   {
      if (CQueue_IsFull(pme->m_pqMediaData) || !CQueue_IsPutOk(pme->m_pqMediaData, (int32)pme->m_dwSrcBufferSize))
         break;

      nRead = ISOURCE_Read(pSource, (char *)pme->m_pSrcBuffer, (int32)pme->m_dwSrcBufferSize);

      if (ISOURCE_WAIT == nRead)
      {
         ISOURCE_Readable(pSource, pme->m_pcbMediaData);
         break;
      }

      if (ISOURCE_END == nRead || ISOURCE_Exhausted(nRead))
      {
         FARF(SOURCE, ("===ReadSrc: EOS %d", nRead));
         pme->m_bEOS = TRUE;
         break;
      }

      if (!CQueue_Put(pme->m_pqMediaData, pme->m_pSrcBuffer, nRead))
      {
         FARF(SOURCE, ("===ReadSrc: Put error nRead=%l", nRead));
         break;
      }

      bNotify = TRUE;
   }

   if (bNotify)
   {
      FARF(SOURCE, ("===ReadSrc: Proc CMXRq"));
      CMediaMMLayer_CMXDataReqNotify(pme);
   }
}

/*==================================================================
   Assumption: pme and ph are not NULL.
==================================================================*/
int CMediaMMLayer_SetSource(CMediaCMX * pme, cmx_handle_type * ph)
{
   AEEMedia *     pMedia = (AEEMedia *)pme;
   AEEMediaData * pmd = &pMedia->m_md;
   int            nRet = SUCCESS;

   if (pmd->clsData == MMD_FILE_NAME)
   {
      ph->efs.source_type = CMX_SOURCE_EFS;
      if (pmd->pData)
         strlcpy(ph->efs.filename, pMedia->m_pszFile, sizeof(ph->efs.filename));
      else
         nRet = EBADPARM;
   }
   else if (pmd->clsData == MMD_BUFFER)
   {
      ph->mem.source_type = CMX_SOURCE_MEM;
      if ( pmd->pData )
      {
         ph->mem.buf = (byte *)pmd->pData;
         /* 
            OEM NOTE: Backward Compatibility
            ISOUNDPLAYER_Set with SDT_BUFFER workaround
            if the size was set to 16, then ISOUNDPLAYER
            Placed 16 as a key to calculate the size of the media
            Please determine the type of media, then the size of the
            media and fill hType.dwLength with the new value 
         */
         /*
           if( pmd->dwSize == 16 ){
             uint32 nType;
             nType = GetMediaFormat(pmd->pData); // Write this function
             switch( nType ){
             case 0x444D4D4D:
               //calculate SMAF MMF data size, inline or in function
               pmd->dwSize = GetMMFSMAFSize(pmd->pData);// Write this function
             case 0x6468544D:
               // calculate MIDI data size, inline or in function
               pmd->dwSize = GetMidiSize(pmd->pData);// Write this function
             case ...: // Other OEM known formats
               pmd->dwSize = GetOtherSize(pmd->pData);// Write these functions
             ...
             }
          } 
         */
         ph->mem.len = pmd->dwSize;
      }
      else
         nRet = EBADPARM;
   }

   if (pmd->clsData >= MMD_ISOURCE)
   {
      // Perform streaming....
      CMediaBg *  pBg = (CMediaBg *)pme;

      if (AEECLSID_MEDIAMIDIOUTQCP == pme->m_clsSelf || 
          AEECLSID_MEDIAMIDIOUTMSG == pme->m_clsSelf)
      {
          return EUNSUPPORTED;
      }

      if (OEMMM_ISSTREAMING(pBg))
         CMediaMMLayer_FlushStreamData(pBg);
      else
      {
         uint32   dwBufSize = pme->m_dwBufferSize ? pme->m_dwBufferSize : OEMMM_BUFFER_SIZE_MAX;
         FARF(SOURCE, ("===Src setup: Start"));
         if (CQueue_New(OEMMM_CMX_REQ_SIZE_MAX, TRUE, &pBg->m_pqCMXDataReq))
            return ENOMEMORY;

         if (CQueue_New(dwBufSize, FALSE, &pBg->m_pqMediaData))
            return ENOMEMORY;
      
         pBg->m_pcbCMXDataReq = MALLOC(sizeof(AEECallback));
         if (!pBg->m_pcbCMXDataReq)
            return ENOMEMORY;
         CALLBACK_Init(pBg->m_pcbCMXDataReq, (PFNNOTIFY)CMediaMMLayer_CMXDataReqNotify, (void *)pBg);

         pBg->m_pcbMediaData = MALLOC(sizeof(AEECallback));
         if (!pBg->m_pcbMediaData)
            return ENOMEMORY;
         CALLBACK_Init(pBg->m_pcbMediaData, (PFNNOTIFY)CMediaMMLayer_ReadSource, (void *)pBg);

         pBg->m_dwSrcBufferSize = MIN(OEMMM_BUFFER_UNIT_SIZE, dwBufSize);
         pBg->m_pSrcBuffer = MALLOC(pBg->m_dwSrcBufferSize);
         if (!pBg->m_pSrcBuffer)
            return ENOMEMORY;

         FARF(SOURCE, ("===Src setup: Done"));
         pBg->m_bStreaming = TRUE;
      }

      ph->client.source_type = ph->source_type = CMX_SOURCE_CLIENT;
      ph->client.data_config = CMX_CLIENT_DATA_REAL_TIME;
      ph->client.data_len = 0xFFFFffff;   // Set it to max.
      ph->client.data_req_ptr = CMediaMMLayer_CMXDataRequestCB;

      CMediaMMLayer_ReadSource(pBg);
   }

   return nRet;
}

/*==================================================================

==================================================================*/
static void CMediaMMLayer_FlushStreamData(CMediaBg * pme)
{
   FARF(SOURCE, ("===Flushing stream data..."));

//   cmx_audio_playback_control(CMX_AUDIO_PB_CMD_FLUSH, NULL, NULL);

   // Cancel pending callbacks...
   CALLBACK_Cancel(pme->m_pcbMediaData);
   CALLBACK_Cancel(pme->m_pcbCMXDataReq);

   // Flush source data queue.
   CQueue_Flush(pme->m_pqMediaData);

   // Flush CMX buffers...
   while (!CQueue_IsEmpty(pme->m_pqCMXDataReq))
   {
      CMXDataRequest req;

      if (!CQueue_Get(pme->m_pqCMXDataReq, (uint8 *)&req, sizeof(req)))
         break;

      OEMMEDIA_SetParm(pme->m_hMediaCMX, OEMCMX_ID_DATA_RESPONSE, (int32)&req, (int32)NULL, (int32)0);
//      req.pfnDataReq(CMX_SUCCESS, NULL, 0, req.pServerUserData);
   }

   if (pme->m_bEOS)
      pme->m_bEOS = FALSE;
}

/*==================================================================

==================================================================*/
static void CMediaMMLayer_InitCallback(IMedia * po, int nCmd, AEEMediaCallback * pcb, int nMax, PFNNOTIFY pfn)
{
   AEEMedia *  pMedia = (AEEMedia *)po;
   int         i;

   for (i = 0; i < nMax; i++)
   {
      pcb[i].bInUse = FALSE;
      CALLBACK_Init(&pcb[i].cb, pfn, &pcb[i]);
      pcb[i].cmdNotify.clsMedia = pMedia->m_clsSelf;
      pcb[i].cmdNotify.pIMedia = po;
      pcb[i].cmdNotify.nCmd = nCmd;
   }
}

/*==================================================================

==================================================================*/
static AEEMediaCallback * CMediaMMLayer_AllocCallback(AEEMediaCallback * pcb, int nMax, int nCmd, int nSubCmd)
{
   AEEMediaCallback *   pcbRet = NULL;

   OEMMM_ENTER_CRITICAL_SECTION()

   int   i;

   for (i = 0; i < nMax; i++)
   {
      if (!pcb[i].bInUse)
      {
         pcb[i].bInUse = TRUE;
         pcb[i].cmdNotify.nCmd = nCmd;
         pcb[i].cmdNotify.nSubCmd = nSubCmd;
         pcb[i].cmdNotify.pCmdData = NULL;
         pcb[i].cmdNotify.dwSize = 0;
         pcbRet = &pcb[i];
         goto Done;
      }
   }

Done:
   OEMMM_LEAVE_CRITICAL_SECTION()

   return pcbRet;
}

/*==================================================================

==================================================================*/
static void CMediaMMLayer_FreeCallback(AEEMediaCallback * pcb)
{
   OEMMM_ENTER_CRITICAL_SECTION()

   pcb->bInUse = FALSE;
   pcb->cmdNotify.nSubCmd = 0;
   pcb->cmdNotify.pCmdData = NULL;
   pcb->cmdNotify.dwSize = 0;

   OEMMM_LEAVE_CRITICAL_SECTION()
}

static void CMediaMMLayer_CancelCallback(AEEMediaCallback * pcb, int nMax)
{
   OEMMM_ENTER_CRITICAL_SECTION()

   int   i;

   for (i = 0; i < nMax; i++)
   {
      CALLBACK_Cancel(&pcb[i].cb);
   }

   OEMMM_LEAVE_CRITICAL_SECTION()
}

#if defined(FEATURE_AUDIO_FORMAT)

/*===========================================================================

                      CMX: MIDI, MP3, PMD, QCP

===========================================================================*/

#ifdef FEATURE_MULTISEQUENCER
/* List of all class ids that are supported in the MultiSequence API */

static AEECLSID multiSequencerClasses[] = {AEECLSID_MEDIAMIDI,
                                           AEECLSID_MEDIAMMF, 
                                           AEECLSID_MEDIAPMD,
                                           AEECLSID_MEDIAPHR,
                                           AEECLSID_MEDIAXMF};

static boolean CMediaMMLayer_IsMultiSequence(CMediaBg *pme);
#endif /* FEATURE_MULTISEQUENCER */

static int CMediaMMLayer_GetMMStatus(CMediaCMX * pme, cmx_status_type status, cmx_af_server_data_type * psd, OEMMMStatus * peStatus);
static void CMediaMMLayer_ProcessPlayServerData(CMediaCMX * pme, cmx_status_type status, cmx_af_server_data_type * psd, AEEMediaCallback * pcb);

static void CMediaMMLayer_SetMediaSpec(CMediaBg * pme, cmx_af_audio_spec_type * psd, AEEMediaCallback * pcb);
#ifdef FEATURE_WEBAUDIO
extern void CMXImage_Process(CMediaBg * pme, cmx_status_type status, cmx_af_server_data_type * psd);
#endif // FEATURE_WEBAUDIO

/*==================================================================

==================================================================*/
static int CMediaMMLayer_Play(CMediaBg * pme)
{
   cmx_handle_type      h;
   AEEMediaCallback *   pcb;
   int                  nRet = SUCCESS;
   CMXInfo              ci;

   nRet = CMediaMMLayer_SetSource((CMediaCMX *)pme, &h);
   if (nRet != SUCCESS)
      return nRet;

   pcb = CMediaMMLayer_AllocCallback(pme->m_cbPlay, OEMMM_MAX_CB_PLAY, MM_CMD_PLAY, 0);
   if (!pcb)
      return EITEMBUSY;

   // If it is raw data, then select codec and start the playback...
   if (pme->m_bRaw)
      return CMediaMMLayer_PlayCodec(pme, &h, pcb);

   if (pme->m_nAPath != MM_APATH_LOCAL_RINGER)
   {

      cmx_audfmt_start_enum_type eType;
      pcb->cmdNotify.nStatus = MM_STATUS_START;
   
      AEE_ResumeCallback(&pcb->cb, pme->m_pac);

#ifdef FEATURE_MULTISEQUENCER
      /* See if we need to play a multisequence */
      if (CMediaMMLayer_IsMultiSequence(pme))
      {
         cmx_audfmt_multi_param_type sParam;

         sParam.repeat_state = CMX_AUDFMT_REPEAT_DISABLED;
         sParam.repeat_count = 0;
         sParam.repeat_ms = 0;
         sParam.cb_type = CMX_AF_CALLBACK_VALUE;

         ci.nType = OEMCMX_TYPE_AUDFMT_PLAY_MULTI;
         ci.ph = &h;
         ci.dwCB = (uint32)CMediaMMLayer_PlayCB;
         ci.dwUserData = (uint32)pme->m_hObject;
         OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_TYPE, (int32)&ci, (int32)&sParam, 0);
         OEMMEDIA_Start(pme->m_hMediaCMX);
      }
      else
#endif  // FEATURE_MULTISEQUENCER
      {
         if (pme->m_clsSelf != AEECLSID_MEDIAQCP)
            eType = CMX_AUDFMT_START_NORMAL;
         else
         {
            if (pme->m_nAPath == MM_APATH_LOCAL)
               eType = CMX_AUDFMT_START_PATH_FORWARD;
            else if (pme->m_nAPath == MM_APATH_REMOTE)
               eType = CMX_AUDFMT_START_PATH_REVERSE;
            else if (pme->m_nAPath == MM_APATH_BOTH)
               eType = CMX_AUDFMT_START_PATH_BOTH;
            else
               eType = CMX_AUDFMT_START_NORMAL;
         }
   
         pcb->cmdNotify.nStatus = MM_STATUS_START;
         AEE_ResumeCallback(&pcb->cb, pme->m_pac);
  
         ci.nType = OEMCMX_TYPE_AUDFMT_PLAY;
         ci.ph = &h;
         ci.dwCB = (uint32)CMediaMMLayer_PlayCB;
         ci.dwUserData = (uint32)pme->m_hObject;
         OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_TYPE, (int32)&ci, (int32)eType, 0);
         OEMMEDIA_Start(pme->m_hMediaCMX);
      }   
   }
   else
   {
      cmx_audfmt_start_enum_type eType = (pme->m_clsSelf == AEECLSID_MEDIAQCP) ? CMX_AUDFMT_START_PATH_FORWARD : CMX_AUDFMT_START_NORMAL;

      pcb->cmdNotify.nStatus = MM_STATUS_START;
      AEE_ResumeCallback(&pcb->cb, pme->m_pac);

      ci.nType = OEMCMX_TYPE_AUDFMT_RINGER;
      ci.ph = &h;
      ci.dwCB = (uint32)CMediaMMLayer_RingerCB;
      ci.dwUserData = (uint32)pme->m_hObject;
      OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_TYPE, (int32)&ci, (int32)eType, (int32)pme->m_wSilenceTimeMS);
      OEMMEDIA_Start(pme->m_hMediaCMX);
   }
#ifdef FEATURE_WEBAUDIO
   if (pme->m_clsSelf == AEECLSID_MEDIAPMD && pme->u.m_pPMD)
   {
      int i;
      TextBlock * pBlock, * pOldBlock;
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbPMDImage);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbJPEGBufAlloc);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbJPEGBufFree);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbClientBufAlloc);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbClientBufFree);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbPicInitialize);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbPicRelease);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbPMDText);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbCopyBMPBuf);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbCopyPNGBuf);      
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbCopyJPEGBuf);
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbSAFCreateDIB);     
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbSAFLoadFrame);

      if(pme->u.m_pPMD->m_pISafBitmap) {
         if(pme->u.m_pPMD->m_pISafBitmap->cntRGB)
            FREEIF(pme->u.m_pPMD->m_pISafBitmap->pRGB);
         IDIB_Release(pme->u.m_pPMD->m_pISafBitmap);
         pme->u.m_pPMD->m_pISafBitmap = NULL;
      }

      if(pme->u.m_pPMD->m_pIPicBitmap) {
         if(pme->u.m_pPMD->m_pIPicBitmap->cntRGB)
            FREEIF(pme->u.m_pPMD->m_pIPicBitmap->pRGB);
         IDIB_Release(pme->u.m_pPMD->m_pIPicBitmap);
         pme->u.m_pPMD->m_pIPicBitmap = NULL;
      }

      pme->u.m_pPMD->m_pIDispBitmap = NULL;

      pBlock = pme->u.m_pPMD->m_textBlock;
      while (pBlock)
      {
         FREEIF(pBlock->m_text);
         pOldBlock = pBlock;
         pBlock = pOldBlock->m_next;
         FREEIF(pOldBlock);
      }
      pme->u.m_pPMD->m_textBlock = NULL;
      pme->u.m_pPMD->m_numToWipe = 0;
      pme->u.m_pPMD->m_fTextDraw |= OEMMM_DISP_CLEAR;

      FreeJPEGBuf(pme);

      for (i=0; i<pme->u.m_pPMD->m_numClientBufs; ++i)
         FREEIF(pme->u.m_pPMD->m_ppClientBufs[i]);

      pme->u.m_pPMD->m_displayedPic = FALSE;
      pme->u.m_pPMD->m_displayedSaf = FALSE;
      pme->u.m_pPMD->m_transparency = FALSE;
   }
#endif

   return nRet;
}

/*==================================================================

==================================================================*/
static int CMediaMMLayer_PlayCodec(CMediaBg * pme, cmx_handle_type * ph, AEEMediaCallback * pcb)
{
#if defined(FEATURE_CMX_RAW_PLAYBACK)
   cmx_codec_spec_type  cs;
   int                  nErr = SUCCESS;

   FARF(MEDIA, ("===Play codec"));
   switch (pme->m_clsSelf)
   {
#if defined(FEATURE_QCP)
      case AEECLSID_MEDIAQCP:
      {
         int   nCodec;

         FARF(MEDIA, ("===Select QCP codec"));
         CMediaMMLayer_GetQCPFormat(pme->u.m_pQCP->m_nFormat, (int *)&nCodec);
         cs.qcp_codec.file_type = (cmx_af_file_enum_type)nCodec;
         if (-1 == cs.qcp_codec.file_type)
            nErr = EINVALIDFORMAT;
         break;
      }
#endif //defined(FEATURE_QCP)

#if defined(FEATURE_MP3)
      case AEECLSID_MEDIAMP3:
      {
         FARF(MEDIA, ("===Select MP3 codec"));
         cs.qcp_codec.file_type = CMX_AF_FILE_MP3;
         break;
      }
#endif //defined(FEATURE_MP3)

#if defined(FEATURE_CMX_LINEAR_PCM)
      case AEECLSID_MEDIAPCM: // Fall thru...
      case AEECLSID_MEDIAADPCM:
      {
         AEEMediaWaveSpec *   pws = (AEEMediaWaveSpec *)pme->m_pSpec;

         FARF(MEDIA, ("===Select Wave codec"));
         if (!pws || !pme->m_dwSpecSize)
            nErr = EBADPARM;
         else
         {
            cs.wave_codec.file_type       = CMX_AF_FILE_WAVE;
            cs.wave_codec.format          = CMX_AF_WAVE_FORMAT_LINEAR_PCM;
            cs.wave_codec.sample_format   = pws->bUnsigned ? CMX_AF_WAVE_SAMPLE_FMT_UNSIGNED : CMX_AF_WAVE_SAMPLE_FMT_SIGNED;
            cs.wave_codec.sample_rate     = pws->dwSamplesPerSec;
            cs.wave_codec.bits_per_sample = pws->wBitsPerSample;
            cs.wave_codec.channels        = pws->wChannels;
         }
         break;
      }
#endif

      default:
         nErr = EFAILED;
   }

   if (!nErr)
   {
      CMXInfo  ci;

      FARF(MEDIA, ("===Play codec"));

      // $br: BRIDLEize this function.
      ci.nType = OEMCMX_TYPE_AUDFMT_PLAY_CODEC;
      ci.ph = ph;
      ci.dwCB = (uint32)CMediaMMLayer_RingerCB;
      ci.dwUserData = (uint32)pme->m_hObject;
      OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_TYPE, (int32)&ci, (int32)&cs, 0);
      OEMMEDIA_Start(pme->m_hMediaCMX);

//      cmx_audfmt_play_codec(ph, &cs, CMediaMMLayer_RingerCB, (void *)pme->m_hObject);
      pcb->cmdNotify.nStatus = MM_STATUS_START;
      AEE_ResumeCallback(&pcb->cb, pme->m_pac);
   }
   else
      CMediaMMLayer_FreeCallback(pcb);

   return nErr;

#else

   return EUNSUPPORTED;

#endif // defined(FEATURE_CMX_RAW_PLAYBACK)
}

/*==================================================================

==================================================================*/
static int CMediaMMLayer_Stop(CMediaBg * pme)
{
   AEEMediaCallback *   pcb = &pme->m_cbCommand;
//   int                  nCmd;

   // Callback must always be available for stop command.
   CMediaMMLayer_FreeCallback(pcb);
//   nCmd = AEEMedia_IsPlayOn(pme) ? MM_CMD_PLAY : MM_CMD_RECORD;
//   CMediaMMLayer_AllocCallback(&pme->m_cbCommand, 1, nCmd, OEMMM_CTLCMD_STOP);

   // Media is stopping...do not process callback data.
   pme->m_bStopping = TRUE;

   //Cancel the callback to draw
   #if defined(FEATURE_WEBAUDIO)
   if (pme->m_clsSelf == AEECLSID_MEDIAPMD && pme->u.m_pPMD)
      CALLBACK_Cancel(&pme->u.m_pPMD->m_cbPMDImage);
   #endif // defined(FEATURE_WEBAUDIO)

   if (pme->m_clsSelf == AEECLSID_MEDIAQCP && AEEMedia_IsRecOn(pme))
   {
#if defined(FEATURE_QCP)
      OEMMEDIA_Stop(pme->m_hMediaCMX);
#else
      return EFAILED;
#endif // defined(FEATURE_QCP)
   }
else if (pme->m_clsSelf == AEECLSID_MEDIAPCM || (pme->m_clsSelf == AEECLSID_MEDIAADPCM && AEEMedia_IsRecOn(pme)))
   {
#if defined(FEATURE_PCM_REC)
      cmx_pcm_rec_link_type link;
      
      if (pme->m_nAPath == MM_APATH_LOCAL)
         link = CMX_PCM_REC_LINK_REVERSE;
      else if (pme->m_nAPath == MM_APATH_REMOTE)
         link = CMX_PCM_REC_LINK_FORWARD;
      else
         link = CMX_PCM_REC_LINK_INVALID;
      
      if (link != CMX_PCM_REC_LINK_INVALID)
      {
         OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_LINK, (int32)link, 0, 0);
      }
      else
      {
         return EBADPARM;
      }

      OEMMEDIA_Stop(pme->m_hMediaCMX);
#else
      return EFAILED;
#endif // defined(FEATURE_PCM_REC)
   }
   
   else
   {
#ifdef FEATURE_MULTISEQUENCER
      /* Check if it is a multisequence */
      if (CMediaMMLayer_IsMultiSequence(pme))
      {
          cmx_audfmt_seq_param_type seqParam;
          seqParam.cmd = CMX_AUDFMT_SEQ_CMD_STOP;
          seqParam.num = 0;
          OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_SEQUENCE, (int32)pme->m_pSequence, (int32)&seqParam, 0);
      }
#endif  // FEATURE_MULTISEQUENCER

         OEMMEDIA_Stop(pme->m_hMediaCMX);
   }

   return SUCCESS;
}

/*==================================================================

==================================================================*/
static int CMediaMMLayer_Seek(CMediaBg * pme, AEEMediaSeek eSeek, int32 lTimeMS)
{
   AEEMediaCallback *   pcb;

   pcb = CMediaMMLayer_AllocCallback(&pme->m_cbCommand, 1, MM_CMD_PLAY, OEMMM_CTLCMD_SEEK);
   if (!pcb)
      return EITEMBUSY;

   switch (eSeek)
   {
      case MM_SEEK_CURRENT:
#ifdef FEATURE_MULTISEQUENCER
          /* See if it is a multisequence */
          if (CMediaMMLayer_IsMultiSequence(pme))
          {
             cmx_audfmt_seq_param_type seqParam;
             seqParam.cmd = lTimeMS < 0 ? CMX_AUDFMT_SEQ_CMD_REWIND : CMX_AUDFMT_SEQ_CMD_FFORWARD;
             seqParam.num = lTimeMS < 0 ? -lTimeMS : lTimeMS;
             OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_SEQUENCE, (int32)pme->m_pSequence, (int32)&seqParam, 0);
          }
#endif  // FEATURE_MULTISEQUENCER          

         OEMMEDIA_Seek(pme->m_hMediaCMX, MM_SEEK_CURRENT, lTimeMS);
         break;

      case MM_SEEK_START:
      case MM_SEEK_END:
      {
         CMediaMMLayer_FreeCallback(pcb);
         return EUNSUPPORTED;
      }
   }

   return SUCCESS;
}

/*==================================================================

==================================================================*/
static int CMediaMMLayer_Pause(CMediaBg * pme)
{
   AEEMediaCallback *   pcb;
   int                  nCmd;

   nCmd = AEEMedia_IsPlayOn(pme) ? MM_CMD_PLAY : MM_CMD_RECORD;
   pcb = CMediaMMLayer_AllocCallback(&pme->m_cbCommand, 1, nCmd, OEMMM_CTLCMD_PAUSE);
   if (!pcb)
      return EITEMBUSY;

   if (pme->m_clsSelf == AEECLSID_MEDIAQCP && AEEMedia_IsRecOn(pme))
   {
#if defined(FEATURE_QCP)
      OEMMEDIA_Pause(pme->m_hMediaCMX);
#else
      return EFAILED;
#endif // defined(FEATURE_QCP)
   }
else if (pme->m_clsSelf == AEECLSID_MEDIAPCM || (pme->m_clsSelf == AEECLSID_MEDIAADPCM && AEEMedia_IsRecOn(pme)))
   {
#if defined(FEATURE_PCM_REC)
      cmx_pcm_rec_link_type link;
      
      if (pme->m_nAPath == MM_APATH_LOCAL)
         link = CMX_PCM_REC_LINK_REVERSE;
      else if (pme->m_nAPath == MM_APATH_REMOTE)
         link = CMX_PCM_REC_LINK_FORWARD;
      else
         link = CMX_PCM_REC_LINK_INVALID;
      
      if (link != CMX_PCM_REC_LINK_INVALID)
      {
         OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_LINK, (int32)link, 0, 0);
      }
      else
      {
         return EBADPARM;
      }

      OEMMEDIA_Pause(pme->m_hMediaCMX);
#else
      return EFAILED;
#endif // defined(FEATURE_PCM_REC)
   }
   else
   {
#ifdef FEATURE_MULTISEQUENCER
      /* See if it is a multisequence */
      if (CMediaMMLayer_IsMultiSequence(pme))
      {
         cmx_audfmt_seq_param_type seqParam;
         seqParam.cmd = CMX_AUDFMT_SEQ_CMD_PAUSE;
         seqParam.num = 0; 
         OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_SEQUENCE, (int32)pme->m_pSequence, (int32)&seqParam, 0);         
      }
#endif

      OEMMEDIA_Pause(pme->m_hMediaCMX);
   }
   return SUCCESS;
}

/*==================================================================

==================================================================*/
static int CMediaMMLayer_Resume(CMediaBg * pme)
{
   AEEMediaCallback *   pcb;
   int                  nCmd;

   nCmd = AEEMedia_IsPlayOn(pme) ? MM_CMD_PLAY : MM_CMD_RECORD;
   pcb = CMediaMMLayer_AllocCallback(&pme->m_cbCommand, 1, nCmd, OEMMM_CTLCMD_RESUME);
   if (!pcb)
      return EITEMBUSY;

   if (pme->m_clsSelf == AEECLSID_MEDIAQCP && AEEMedia_IsRecOn(pme))
   {
#if defined(FEATURE_QCP)
     OEMMEDIA_Resume(pme->m_hMediaCMX);
#else
      return EFAILED;
#endif // defined(FEATURE_QCP)
   }
   else if (pme->m_clsSelf == AEECLSID_MEDIAPCM || (pme->m_clsSelf == AEECLSID_MEDIAADPCM && AEEMedia_IsRecOn(pme)))
   {
#if defined(FEATURE_PCM_REC)
      cmx_pcm_rec_link_type link;
      
      if (pme->m_nAPath == MM_APATH_LOCAL)
         link = CMX_PCM_REC_LINK_REVERSE;
      else if (pme->m_nAPath == MM_APATH_REMOTE)
         link = CMX_PCM_REC_LINK_FORWARD;
      else
         link = CMX_PCM_REC_LINK_INVALID;
      
      if (link != CMX_PCM_REC_LINK_INVALID)
      {
         OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_LINK, (int32)link, 0, 0);
      }
      else
      {
         return EBADPARM;
      }

      OEMMEDIA_Resume(pme->m_hMediaCMX);
#else
      return EFAILED;
#endif // defined(FEATURE_PCM_REC)
   }
   else
   {
#ifdef FEATURE_MULTISEQUENCER
      if (CMediaMMLayer_IsMultiSequence(pme))
      {
         cmx_audfmt_seq_param_type seqParam;
         seqParam.cmd = CMX_AUDFMT_SEQ_CMD_RESUME;
         seqParam.num = 0;
         OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_SEQUENCE, (int32)pme->m_pSequence, (int32)&seqParam, 0);          
      }
#endif  // FEATURE_MULTISEQUENCER
      OEMMEDIA_Resume(pme->m_hMediaCMX);
   }
   
   return SUCCESS;
}

/*==================================================================

==================================================================*/
static int CMediaMMLayer_GetTotalTime(CMediaBg * pme)
{
   cmx_handle_type      h;
   AEEMediaCallback *   pcb;
   int                  nRet = SUCCESS;
   CMXInfo              ci;

   nRet = CMediaMMLayer_SetSource((CMediaCMX *)pme, &h);
   if (nRet != SUCCESS)
      return nRet;

   pcb = CMediaMMLayer_AllocCallback(&pme->m_cbGetTotalTime, 1, MM_CMD_GETTOTALTIME, 0);
   if (!pcb)
      return EITEMBUSY;

   ci.nType = OEMCMX_TYPE_AUDFMT_PLAY;
   ci.ph = &h;
   ci.dwCB = (uint32)(int32)CMediaMMLayer_TimeCB;
   ci.dwUserData = (uint32)pme->m_hObject;
   OEMMEDIA_SetParm(pme->m_hMediaCMX, OEMCMX_ID_TOTALTIME, (int32)&ci, 0, 0);

   return nRet;
}

/*==================================================================

==================================================================*/
static int CMediaMMLayer_SetTempo(CMediaBg * pme, uint32 dwTempo)
{
   AEEMediaCallback *   pcb;

   pcb = CMediaMMLayer_AllocCallback(&pme->m_cbCommand, 1, MM_CMD_SETMEDIAPARM, MM_PARM_TEMPO);
   if (!pcb)
      return EITEMBUSY;

   pme->m_wTempo = (uint16)dwTempo;

#ifdef FEATURE_MULTISEQUENCER
   if (CMediaMMLayer_IsMultiSequence(pme))
   {
      cmx_audfmt_seq_param_type seqParam;
      seqParam.cmd = CMX_AUDFMT_SEQ_CMD_TEMPO;
      seqParam.num = dwTempo;
      OEMMEDIA_SetParm(pme->m_hMediaCMX, OEMCMX_ID_SEQUENCE, (int32)pme->m_pSequence, (int32)&seqParam, 0);      
   }
   else
#endif  // FEATURE_MULTISEQUENCER

   OEMMEDIA_SetParm(pme->m_hMediaCMX, OEMCMX_ID_TEMPO, (int32)dwTempo, 0, 0);

   return MM_PENDING;
}

/*==================================================================

==================================================================*/
static int CMediaMMLayer_SetTune(CMediaBg * pme, uint32 dwTune)
{
   AEEMediaCallback *   pcb;

   pcb = CMediaMMLayer_AllocCallback(&pme->m_cbCommand, 1, MM_CMD_SETMEDIAPARM, MM_PARM_TUNE);
   if (!pcb)
      return EITEMBUSY;

   pme->m_wTune = (uint16)dwTune;

#ifdef FEATURE_MULTISEQUENCER
   if (CMediaMMLayer_IsMultiSequence(pme))
   {
      cmx_audfmt_seq_param_type seqParam;
      seqParam.cmd = CMX_AUDFMT_SEQ_CMD_TUNE;
      seqParam.num = dwTune;
      OEMMEDIA_SetParm(pme->m_hMediaCMX, OEMCMX_ID_SEQUENCE, (int32)pme->m_pSequence, (int32)&seqParam, 0);      
   }
   else
#endif  // FEATURE_MULTISEQUENCER
      OEMMEDIA_SetParm(pme->m_hMediaCMX, OEMCMX_ID_TUNE, (int32)dwTune, 0, 0);

   return MM_PENDING;
}

/*==================================================================

==================================================================*/
static int CMediaMMLayer_SetPan(CMediaBg * pme, uint32 dwPan)
{
   AEEMediaCallback *   pcb;

#ifdef FEATURE_MULTISEQUENCER    // Pan is currently unsupported
   if (CMediaMMLayer_IsMultiSequence(pme))
   {
      return EUNSUPPORTED;
   }
#endif // FEATURE_MULTISEQUENCER

   if (dwPan > MM_MAX_PAN)
      return EBADPARM;

   pcb = CMediaMMLayer_AllocCallback(&pme->m_cbCommand, 1, MM_CMD_SETMEDIAPARM, MM_PARM_PAN);
   if (!pcb)
      return EITEMBUSY;

#if !defined(AEE_SIMULATOR)
   pme->m_wPan = (uint16)dwPan;

   OEMMEDIA_SetParm(pme->m_hMediaCMX, OEMCMX_ID_PAN, (int32)dwPan, 0, 0);

   return MM_PENDING;
#else
   CMediaMMLayer_FreeCallback(pcb);
   return EFAILED;
#endif //!defined(AEE_SIMULATOR)
}

static int CMediaMMLayer_QCPRecord(CMediaBg * pme, int nFormat)
{
#if !defined(FEATURE_QCP)
   return EFAILED;
#else
   cmx_handle_type      h;
   AEEMediaCallback *   pcb;
   int                  nRet = SUCCESS;
   cmx_qcp_format_type  fmt = (cmx_qcp_format_type)CMediaMMLayer_GetQCPFormat(nFormat, NULL);

   if (fmt >= (int)CMX_QCP_FORMAT_INVALID)
      return EBADPARM;

   nRet = CMediaMMLayer_SetSource((CMediaCMX *)pme, &h);
   if (nRet != SUCCESS)
      return nRet;

   pcb = CMediaMMLayer_AllocCallback(pme->m_cbPlay, OEMMM_MAX_CB_PLAY, MM_CMD_RECORD, 0);
   if (!pcb)
      return EITEMBUSY;

#if defined(FEATURE_BREW_CMX_V3)
   {
      cmx_qcp_rec_para_type qcp_rec_param;
      qcp_rec_param.auto_stop_ms = 0;
      qcp_rec_param.report_ms = pme->m_dwTickTimeMS;
      qcp_rec_param.format = fmt;

      if (pme->m_nAPath == MM_APATH_LOCAL || pme->m_nAPath == MM_APATH_REMOTE)
      {
         CMXInfo  ci;

         ci.nType = OEMCMX_TYPE_QCP_RECORD;
         ci.ph = &h;
         ci.dwCB = (uint32)CMediaMMLayer_RecordCB;
         ci.dwUserData = (uint32)pme->m_hObject;
         OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_TYPE, (int32)&ci, (int32)&qcp_rec_param, (pme->m_nAPath == MM_APATH_LOCAL));
         OEMMEDIA_Start(pme->m_hMediaCMX);
      }
      else
      {
         CMediaMMLayer_FreeCallback(pcb);
         return EBADPARM;
      }
   }
#else // CMX 2.x
   if (pme->m_nAPath == MM_APATH_LOCAL)
      cmx_qcp_record_reverse(&h, fmt, pme->m_dwTickTimeMS, CMediaMMLayer_RecordCB, (void *)pme->m_hObject);
   else if (pme->m_nAPath == MM_APATH_REMOTE)
      cmx_qcp_record_forward(&h, fmt, pme->m_dwTickTimeMS, CMediaMMLayer_RecordCB, (void *)pme->m_hObject);
   else
   {
      CMediaMMLayer_FreeCallback(pcb);
      return EBADPARM;
   }
#endif // defined(FEATURE_BREW_CMX_V3)

   pcb->cmdNotify.nStatus = MM_STATUS_START;
   AEE_ResumeCallback(&pcb->cb, pme->m_pac);

   return SUCCESS;
#endif // !defined(FEATURE_QCP)
}

//$r$
#if defined(FEATURE_QCP)
static int CMediaMMLayer_GetQCPFormat(int nFormat, int * pCodec)
{
   int   fmt;
   int   nCodec = -1;

#if defined(FEATURE_BREW_CMX_V3)
   switch (nFormat)
   {
      case MM_QCP_FORMAT_FIXED_FULL_13K:  fmt = CMX_QCP_FORMAT_FIXED_FULL_13K;   nCodec = CMX_AF_FILE_QCP_13K; break;
      case MM_QCP_FORMAT_VAR_FULL_13K:    fmt = CMX_QCP_FORMAT_VAR_FULL_13K;     nCodec = CMX_AF_FILE_QCP_13K; break;
      case MM_QCP_FORMAT_VAR_HALF_13K:    fmt = CMX_QCP_FORMAT_VAR_HALF_13K;     nCodec = CMX_AF_FILE_QCP_13K; break;
      case MM_QCP_FORMAT_FIXED_HALF_13K:  fmt = CMX_QCP_FORMAT_FIXED_HALF_13K;   nCodec = CMX_AF_FILE_QCP_13K; break;
      case MM_QCP_FORMAT_FIXED_FULL_EVRC: fmt = CMX_QCP_FORMAT_FIXED_FULL_EVRC;  nCodec = CMX_AF_FILE_QCP_EVRC; break;
      case MM_QCP_FORMAT_VAR_FULL_EVRC:   fmt = CMX_QCP_FORMAT_VAR_FULL_EVRC;    nCodec = CMX_AF_FILE_QCP_EVRC; break;

      case MM_QCP_FORMAT_FIXED_4_75_AMR:  fmt = CMX_QCP_FORMAT_FIXED_4_75_AMR;   nCodec = CMX_AF_FILE_QCP_AMR; break;
      case MM_QCP_FORMAT_FIXED_5_15_AMR:  fmt = CMX_QCP_FORMAT_FIXED_5_15_AMR;   nCodec = CMX_AF_FILE_QCP_AMR; break;
      case MM_QCP_FORMAT_FIXED_5_9_AMR :  fmt = CMX_QCP_FORMAT_FIXED_5_9_AMR ;   nCodec = CMX_AF_FILE_QCP_AMR; break;
      case MM_QCP_FORMAT_FIXED_6_7_AMR :  fmt = CMX_QCP_FORMAT_FIXED_6_7_AMR ;   nCodec = CMX_AF_FILE_QCP_AMR; break;
      case MM_QCP_FORMAT_FIXED_7_4_AMR :  fmt = CMX_QCP_FORMAT_FIXED_7_4_AMR ;   nCodec = CMX_AF_FILE_QCP_AMR; break;
      case MM_QCP_FORMAT_FIXED_7_95_AMR:  fmt = CMX_QCP_FORMAT_FIXED_7_95_AMR;   nCodec = CMX_AF_FILE_QCP_AMR; break;
      case MM_QCP_FORMAT_FIXED_10_2_AMR:  fmt = CMX_QCP_FORMAT_FIXED_10_2_AMR;   nCodec = CMX_AF_FILE_QCP_AMR; break;
      case MM_QCP_FORMAT_FIXED_12_2_AMR:  fmt = CMX_QCP_FORMAT_FIXED_12_2_AMR;   nCodec = CMX_AF_FILE_QCP_AMR; break;
#ifdef FEATURE_AUDFMT_AMR      
      case MM_AMR_FORMAT_FIXED_4_75_AMR:  fmt = CMX_AMR_FORMAT_FIXED_4_75_AMR;   break;
      case MM_AMR_FORMAT_FIXED_5_15_AMR:  fmt = CMX_AMR_FORMAT_FIXED_5_15_AMR;   break;
      case MM_AMR_FORMAT_FIXED_5_9_AMR :  fmt = CMX_AMR_FORMAT_FIXED_5_9_AMR ;   break;
      case MM_AMR_FORMAT_FIXED_6_7_AMR :  fmt = CMX_AMR_FORMAT_FIXED_6_7_AMR ;   break;
      case MM_AMR_FORMAT_FIXED_7_4_AMR :  fmt = CMX_AMR_FORMAT_FIXED_7_4_AMR ;   break;
      case MM_AMR_FORMAT_FIXED_7_95_AMR:  fmt = CMX_AMR_FORMAT_FIXED_7_95_AMR;   break;
      case MM_AMR_FORMAT_FIXED_10_2_AMR:  fmt = CMX_AMR_FORMAT_FIXED_10_2_AMR;   break;
      case MM_AMR_FORMAT_FIXED_12_2_AMR:  fmt = CMX_AMR_FORMAT_FIXED_12_2_AMR;   break;
#endif	 
      default:                            fmt = CMX_QCP_FORMAT_INVALID;          break;
   }
#else // CMX 2.x
   fmt = nFormat;
#endif // defined(FEATURE_BREW_CMX_V3)

   if (pCodec)
      *pCodec = nCodec;

   return fmt;
}
#endif // defined(FEATURE_QCP)

static int CMediaMMLayer_PCMRecord(CMediaBg * pme)
{
#if !defined(FEATURE_PCM_REC)
   return EFAILED;
#else
   cmx_handle_type      h;
   AEEMediaCallback *   pcb;
   int                  nRet = SUCCESS;
   cmx_pcm_rec_para_type  pcmParm;

   nRet = CMediaMMLayer_SetSource((CMediaCMX *)pme, &h);
   if (nRet != SUCCESS)
      return nRet;

   pcb = CMediaMMLayer_AllocCallback(pme->m_cbPlay, OEMMM_MAX_CB_PLAY, MM_CMD_RECORD, 0);
   if (!pcb)
      return EITEMBUSY;

   if (pme->m_nAPath == MM_APATH_LOCAL)
      pcmParm.link = CMX_PCM_REC_LINK_REVERSE;
   else if (pme->m_nAPath == MM_APATH_REMOTE)
      pcmParm.link = CMX_PCM_REC_LINK_FORWARD;
   else
      pcmParm.link = CMX_PCM_REC_LINK_INVALID;
      
   if (pcmParm.link != CMX_PCM_REC_LINK_INVALID)
   {
      CMXInfo  ci;

      ci.nType = OEMCMX_TYPE_PCM_RECORD;
      ci.ph = &h;
      ci.dwCB = (uint32)CMediaMMLayer_RecordCB;
      ci.dwUserData = (uint32)pme->m_hObject;
      OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_TYPE, (int32)&ci, (int32)&pcmParm, 0);
      OEMMEDIA_Start(pme->m_hMediaCMX);
   }
   else
   {
      CMediaMMLayer_FreeCallback(pcb);
      return EBADPARM;
   }

   pcb->cmdNotify.nStatus = MM_STATUS_START;
   AEE_ResumeCallback(&pcb->cb, pme->m_pac);

   return SUCCESS;
#endif // !defined(FEATURE_PCM_REC)
}

//
// CMX callback functions. These functions are called in CMX context.
//
/*==================================================================

==================================================================*/
void CMediaMMLayer_PlayCB(cmx_status_type status, const void * pClientData, cmx_af_server_data_type * psd)
{
   CMediaBg *           pme;
   AEEMediaCallback *   pcb = NULL;
   boolean              bResume = FALSE;

   OEMMM_ENTER_CRITICAL_SECTION()
   
   OEMMMStatus          eStatus;
   int                  nStatus = MM_STATUS_UNKNOWN;

   pme = (CMediaBg *)AEEObjectMgr_GetObject((AEEObjectHandle)pClientData);
   if (!pme)
      goto OEMMM_CBExit;

   BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme, sizeof(CMediaBg));

   nStatus = CMediaMMLayer_GetMMStatus((CMediaCMX *)pme, status, psd, &eStatus);
   if (nStatus == MM_STATUS_UNKNOWN)
      goto OEMMM_CBExit;

   // For playctl command, cbCommand was reserved...free it since
   // SEEK/PAUSE/RESUME/STOP status is sent thru play cb.
   if (eStatus == OEMMM_STATUS_PLAYCTL && CMediaMMLayer_ISPLAYCTL(&pme->m_cbCommand))
   {
      CMediaMMLayer_FreeCallback(&pme->m_cbCommand);
   }

   // For set parm event (tempo, tune, pan), cbCommand is sent in AEE_ResumeCallback().
   // Else for other playback events, one of cbPlay is sent in AEE_ResumeCallback().
   if (eStatus == OEMMM_STATUS_SETPARM) // || eStatus == OEMMM_STATUS_PLAYCTL)
      pcb = &pme->m_cbCommand;
   else
      pcb = CMediaMMLayer_AllocCallback(pme->m_cbPlay, OEMMM_MAX_CB_PLAY, MM_CMD_PLAY, 0);

   if (pcb)
   {
      pcb->cmdNotify.nStatus = nStatus;
      CMediaMMLayer_ProcessPlayServerData((CMediaCMX *)pme, status, psd, pcb);

      bResume = TRUE;
   }

OEMMM_CBExit:

   OEMMM_LEAVE_CRITICAL_SECTION()

   if (bResume)
      AEE_ResumeCallback(&pcb->cb, pme->m_pac);
}

/*==================================================================

==================================================================*/
void CMediaMMLayer_RingerCB(cmx_status_type status, const void * pClientData)
{
   CMediaMMLayer_PlayCB(status, pClientData, NULL);
}

/*==================================================================

==================================================================*/
void CMediaMMLayer_StatusCB(cmx_status_type status, const void * pClientData)
{
   CMediaBg *           pme;
   AEEMediaCallback *   pcb = NULL;
   boolean              bResume = FALSE;

   OEMMM_ENTER_CRITICAL_SECTION()

   int   nStatus = MM_STATUS_UNKNOWN;

   pme = (CMediaBg *)AEEObjectMgr_GetObject((AEEObjectHandle)pClientData);
   if (!pme)
      goto OEMMM_CBExit;

   BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme, sizeof(CMediaBg));

   pcb = &pme->m_cbCommand;
   if (!pcb->bInUse)
      goto OEMMM_CBExit;

   nStatus = CMediaMMLayer_GetMMStatus((CMediaCMX *)pme, status, NULL, NULL);
   if (nStatus == MM_STATUS_UNKNOWN)
      goto OEMMM_CBExit;

   // Check if it is playctl cmd event. If it is and if status is MM_STATUS_ABORT,
   // the status must be mapped to one of SEEK_FAIL/PAUSE_FAIL/RESUME_FAIL
   // bases on nSubCmd.
   if (nStatus == MM_STATUS_ABORT && CMediaMMLayer_ISPLAYCTL(pcb))
   {
      int   nSubCmd = pcb->cmdNotify.nSubCmd;
      pcb->cmdNotify.nSubCmd = 0;   // Do not send this subcmd to user.

      if (nSubCmd == OEMMM_CTLCMD_SEEK)
         nStatus = MM_STATUS_SEEK_FAIL;
      else if (nSubCmd == OEMMM_CTLCMD_PAUSE)
         nStatus = MM_STATUS_PAUSE_FAIL;
      else if (nSubCmd == OEMMM_CTLCMD_RESUME)
         nStatus = MM_STATUS_RESUME_FAIL;
   }

   pcb->cmdNotify.nStatus = nStatus;

   bResume = TRUE;

OEMMM_CBExit:

   OEMMM_LEAVE_CRITICAL_SECTION()

   if (bResume)
      AEE_ResumeCallback(&pcb->cb, pme->m_pac);
}

/*==================================================================

==================================================================*/
void CMediaMMLayer_TimeCB(cmx_status_type status, const void * pClientData, uint32 dwTimeMS)
{
   CMediaBg *           pme;
   AEEMediaCallback *   pcb = NULL;
   boolean              bResume = FALSE;

   OEMMM_ENTER_CRITICAL_SECTION()

   int                  nStatus = MM_STATUS_UNKNOWN;
   pme = (CMediaBg *)AEEObjectMgr_GetObject((AEEObjectHandle)pClientData);
   if (!pme)
      goto OEMMM_CBExit;

   BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme, sizeof(CMediaBg));

   pcb = &pme->m_cbGetTotalTime;

   nStatus = CMediaMMLayer_GetMMStatus((CMediaCMX *)pme, status, NULL, NULL);
   if (nStatus == MM_STATUS_UNKNOWN)
      goto OEMMM_CBExit;

   pcb->cmdNotify.nStatus = nStatus;

   if (nStatus == MM_STATUS_DONE)
   {
      pcb->cmdNotify.pCmdData = (void *)dwTimeMS;
      pcb->cmdNotify.dwSize = sizeof(uint32);
   }

   bResume = TRUE;

OEMMM_CBExit:

   OEMMM_LEAVE_CRITICAL_SECTION()

   if (bResume)
      AEE_ResumeCallback(&pcb->cb, pme->m_pac);

}

/*==================================================================

==================================================================*/
#if defined(FEATURE_QCP) || defined(FEATURE_PCM_REC)
void CMediaMMLayer_RecordCB(cmx_status_type status, const void * pClientData)
{
   CMediaBg *           pme;
   AEEMediaCallback *   pcb = NULL;
   boolean              bResume = FALSE;

   OEMMM_ENTER_CRITICAL_SECTION()

   int                  nStatus = MM_STATUS_UNKNOWN;
   OEMMMStatus          eStatus;

   pme = (CMediaBg *)AEEObjectMgr_GetObject((AEEObjectHandle)pClientData);
   if (!pme)
      goto OEMMM_CBExit;

   BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme, sizeof(CMediaBg));

   nStatus = CMediaMMLayer_GetMMStatus((CMediaCMX *)pme, status, NULL, &eStatus);
   if (nStatus == MM_STATUS_UNKNOWN)
      goto OEMMM_CBExit;

   if (eStatus == OEMMM_STATUS_PLAYCTL)
      pcb = &pme->m_cbCommand;
   else
      pcb = CMediaMMLayer_AllocCallback(pme->m_cbPlay, OEMMM_MAX_CB_PLAY, MM_CMD_RECORD, 0);

   if (pcb)
   {
      pcb->cmdNotify.nStatus = nStatus;
      bResume = TRUE;
   }

OEMMM_CBExit:

   OEMMM_LEAVE_CRITICAL_SECTION()

   if (bResume)
      AEE_ResumeCallback(&pcb->cb, pme->m_pac);
}
#endif // defined(FEATURE_QCP) || defined(FEATURE_PCM_REC)

/*==================================================================

==================================================================*/
#ifdef FEATURE_MULTISEQUENCER
static boolean CMediaMMLayer_IsMultiSequence(CMediaBg *pme)
{
   int i;
   int numClassesSupported = sizeof(multiSequencerClasses)/sizeof(AEECLSID);

   if (pme->m_bChannelShare)
   {
      for (i = 0; i < numClassesSupported; i++)
      {
         if (multiSequencerClasses[i] == pme->m_clsSelf)
         {
            return TRUE;
         }
      }
   }
   return FALSE;
}
#endif  // FEATURE_MULTISEQUENCER 

/*==================================================================

==================================================================*/
static int CMediaMMLayer_GetMMStatus(CMediaCMX * pme, cmx_status_type status, cmx_af_server_data_type * psd, OEMMMStatus * peStatus)
{
//   ACONTEXT *  pacLast = AEE_EnterAppContext(pme->m_pac);

   int   nStatus = MM_STATUS_UNKNOWN;

   if (!pme)
      return nStatus;

   if (peStatus)
      *peStatus = OEMMM_STATUS_PLAY;

   switch (status)
   {
      case CMX_SUCCESS:             
         break;

      case CMX_DONE:                
         nStatus = MM_STATUS_DONE; 
         pme->m_bStopping = FALSE;
         break;

      case CMX_ENABLED: // fall thru...
      case CMX_DISABLED:            
         break;

      case CMX_TICK_UPDATE:
         if (pme->m_dwTickTimeMS)
            nStatus = MM_STATUS_TICK_UPDATE;
         break;

      case CMX_REPEAT:              
         nStatus = MM_STATUS_REPEAT; 
         break;

      case CMX_DATA_ACCESS_DELAY:   
         nStatus = MM_STATUS_DATA_IO_DELAY; 
         break;

      case CMX_ABORTED:             
         nStatus = MM_STATUS_ABORT; 
         pme->m_bStopping = FALSE;
         break;

      case CMX_FLUSHED:  // fall thru...
      case CMX_INVALID_DATA_SOURCE: 
         break;

      case CMX_REWIND: // fall thru...         
      case CMX_FFORWARD:
         nStatus = MM_STATUS_SEEK;
         if (peStatus)
            *peStatus = OEMMM_STATUS_PLAYCTL;
         break;

      case CMX_PAUSE:
         nStatus = MM_STATUS_PAUSE;
         if (peStatus)
            *peStatus = OEMMM_STATUS_PLAYCTL;
         break;

      case CMX_RESUME:              
         nStatus = MM_STATUS_RESUME;
         if (peStatus)
            *peStatus = OEMMM_STATUS_PLAYCTL;
         break;

      case CMX_TEMPO: // fall thru...             
      case CMX_TUNE: // fall thru...                
      case CMX_PAN:                 
         nStatus = MM_STATUS_DONE;
         if (peStatus)
            *peStatus = OEMMM_STATUS_SETPARM;
         break;

      case CMX_AUDIO_SPEC:          
         nStatus = MM_STATUS_MEDIA_SPEC; 
         break;

#ifdef FEATURE_QCP
      case CMX_QCP_SPACE_WARNING:   
         nStatus = MM_STATUS_SPACE_WARNING;
         break;

      case CMX_QCP_SPACE_DONE:      
         nStatus = MM_STATUS_SPACE_ERROR;
         break;
#endif // FEATURE_QCP

      case CMX_ERROR: // fall thru...              
      case CMX_FAILURE:             
         if (pme->m_bStopping)
            pme->m_bStopping = FALSE;
         nStatus = MM_STATUS_ABORT;
         break;

#ifdef FEATURE_WEBAUDIO
      case CMX_TEXT_CTL:
         if (!pme->m_bStopping && psd && ((CMediaBg*)pme)->u.m_pPMD)
         {
            // Make a shallow copy of the data passed (buffer is one of m_ppClientBufs)
            // and queue callback in user mode
            ((CMediaBg*)pme)->u.m_pPMD->m_textCtl = psd->text_ctl;
            AEE_ResumeCallback(&((CMediaBg*)pme)->u.m_pPMD->m_cbPMDText, pme->m_pac);
         }
         break;

      case CMX_CLIENT_BUF_FREE:
      case CMX_CLIENT_BUF_REQ:
#if defined(FEATURE_BREW_CMX_V3)
      case CMX_GET_BUFFER:
#endif // defined(FEATURE_BREW_CMX_V3)
      case CMX_PICTURE_STATUS:       
      case CMX_PICTURE_DATA:
         if (!pme->m_bStopping)
            CMXImage_Process((CMediaBg *)pme, status, psd);
         break;

#ifdef FEATURE_SAF
      case CMX_SAF_INFO:             
      case CMX_SAF_FRAME:            
      case CMX_ANIM_CLEAR:
      case CMX_ANIM_ENABLE:
      case CMX_ANIM_DISABLE:
         if (!pme->m_bStopping)
            CMXImage_Process((CMediaBg *)pme, status, psd);
         break;
#endif // FEATURE_SAF
#endif // FEATURE_WEBAUDIO
   }

   
   return nStatus;
}

/*==================================================================

==================================================================*/
static void CMediaMMLayer_ProcessPlayServerData(CMediaCMX * pme, cmx_status_type status, cmx_af_server_data_type * psd, AEEMediaCallback * pcb)
{
   if (!pcb || !psd)
      return;

   switch (status)
   {
      case CMX_PAUSE:
         pcb->cmdNotify.nSubCmd = OEMMM_CTLCMD_PAUSE;
         pcb->cmdNotify.pCmdData = (void *)psd->elapsed_time_ms;
         pcb->cmdNotify.dwSize = sizeof(psd->elapsed_time_ms);
         break;

      case CMX_RESUME:
         pcb->cmdNotify.nSubCmd = OEMMM_CTLCMD_RESUME;
         pcb->cmdNotify.pCmdData = (void *)psd->elapsed_time_ms;
         pcb->cmdNotify.dwSize = sizeof(psd->elapsed_time_ms);
         break;

      case CMX_REWIND:
      case CMX_FFORWARD:
         pcb->cmdNotify.nSubCmd = OEMMM_CTLCMD_SEEK;
         pcb->cmdNotify.pCmdData = (void *)psd->elapsed_time_ms;
         pcb->cmdNotify.dwSize = sizeof(psd->elapsed_time_ms);
         break;

      case CMX_TEMPO:
         pme->m_wTempo = (uint16)psd->tempo;
         pcb->cmdNotify.dwSize = sizeof(psd->tempo);
         break;

      case CMX_TUNE:
         pme->m_wTune = (uint16)psd->tune;
         pcb->cmdNotify.dwSize = sizeof(psd->tune);
         break;

      case CMX_PAN:
         pme->m_wPan = (uint16)psd->pan;
         pcb->cmdNotify.dwSize = sizeof(psd->pan);
         break;

      case CMX_AUDIO_SPEC:
         CMediaMMLayer_SetMediaSpec((CMediaBg *)pme, (cmx_af_audio_spec_type *)psd, pcb);
         break;

      default:
         break;
   } // switch
}

/*==================================================================

==================================================================*/
static void CMediaMMLayer_SetMediaSpec(CMediaBg * pme, cmx_af_audio_spec_type * psd, AEEMediaCallback * pcb)
{
#if defined(FEATURE_MP3)
   if (AEECLSID_MEDIAMP3 == pme->m_clsSelf && CMX_AF_FILE_MP3 == psd->file_type)
   {
      AEEMediaMP3Spec *       pm = &pme->u.m_pMP3->m_spec;
      cmx_af_mp3_spec_type *  pc = (cmx_af_mp3_spec_type *)psd;

      BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme->u.m_pMP3, sizeof(CMediaMP3));

      pm->nVersion = (int)pc->version;
      pm->nLayer = (byte)pc->layer;
      pm->bCRCFlag = pc->crc_f;
      pm->wBitRate = (uint16)pc->bitrate;

      pm->nChannel = (byte)pc->channel;
      pm->nExtension = (byte)pc->extension;
      pm->bCopyrightFlag = pc->copyright_f;
      pm->bOriginalFlag = pc->original_f;
      pm->nEmphasis = (byte)pc->emphasis;
      MEMCPY(pm->szTitle, pc->title, MIN(sizeof(pm->szTitle), sizeof(pc->title)));
      MEMCPY(pm->szArtist, pc->artist, MIN(sizeof(pm->szArtist), sizeof(pc->artist)));
      MEMCPY(pm->szAlbum, pc->album, MIN(sizeof(pm->szAlbum), sizeof(pc->album)));
      MEMCPY(pm->szYear, pc->year, MIN(sizeof(pm->szYear), sizeof(pc->year)));
      MEMCPY(pm->szComment, pc->comment, MIN(sizeof(pm->szComment), sizeof(pc->comment)));
      pm->nGenre = pc->genre;

      // Convert the sample rate...
      switch (pc->sample_rate)
      {
         case CMX_AF_MP3_SAMPLE_RATE_8000: pm->dwSampleRate = 8000; break;

#if defined(FEATURE_STEREO_DAC)
         case CMX_AF_MP3_SAMPLE_RATE_11025: pm->dwSampleRate = 11025; break;
         case CMX_AF_MP3_SAMPLE_RATE_12000: pm->dwSampleRate = 12000; break;

         case CMX_AF_MP3_SAMPLE_RATE_16000: pm->dwSampleRate = 16000; break;
         case CMX_AF_MP3_SAMPLE_RATE_22050: pm->dwSampleRate = 22050; break;
         case CMX_AF_MP3_SAMPLE_RATE_24000: pm->dwSampleRate = 24000; break;

         case CMX_AF_MP3_SAMPLE_RATE_32000: pm->dwSampleRate = 32000; break;
         case CMX_AF_MP3_SAMPLE_RATE_44100: pm->dwSampleRate = 44100; break;
         case CMX_AF_MP3_SAMPLE_RATE_48000: pm->dwSampleRate = 48000; break;
#endif // defined(FEATURE_STEREO_DAC)

         case CMX_AF_MP3_SAMPLE_RATE_UNKNOWN: // fall thru...
         default:
            pm->dwSampleRate = MM_MP3_SAMPLE_RATE_UNKNOWN; break;
      } // switch
   } // if
#endif // defined(FEATURE_MP3)

#if defined(FEATURE_WEBAUDIO)
   if (psd->file_type == CMX_AF_FILE_CMID)
   {
      BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme->u.m_pPMD, sizeof(CMediaPMD));
      // Store the character set for translating later
      pme->u.m_pPMD->m_char_set = psd->wa_spec.char_set;
   }
#endif

#if defined(FEATURE_BREW_CMX_V3) 

#if defined(FEATURE_ADPCM)
   if (AEECLSID_MEDIAADPCM == pme->m_clsSelf && CMX_AF_FILE_ADPCM == psd->file_type)
   {
      AEEMediaWaveSpec *         pws = &pme->u.m_pADPCM->m_spec;
      cmx_af_adpcm_spec_type *   pc = (cmx_af_adpcm_spec_type *)psd;

      BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme->u.m_pADPCM, sizeof(CMediaADPCM));

      pws->wSize = sizeof(AEEMediaWaveSpec);
      pws->clsMedia = AEECLSID_MEDIAADPCM;
      pws->wChannels = pc->channels;
      pws->dwSamplesPerSec = pc->sample_rate;
      pws->dwBytesPerSec = pc->bytes_rate;
      pws->wBlockSize = pc->frame_size;
      pws->wBitsPerSample = pc->bits_per_sample;
      pws->dwExtra = 0;
   }
#endif // defined(FEATURE_ADPCM)

#if defined(FEATURE_AAC)
   if (AEECLSID_MEDIAAAC == pme->m_clsSelf)
   {
      // AAC media spec
   }
#endif // defined(FEATURE_AAC)
#if defined(FEATURE_IMELODY)
   if (AEECLSID_MEDIAAAC == pme->m_clsSelf)
   {
      // IMelody media spec
   }
#endif // defined(FEATURE_IMELODY)

   if (pme->m_bChannelShare)
   {
#ifdef FEATURE_MIDI
      if (AEECLSID_MEDIAMIDI == pme->m_clsSelf) 
    {
      pme->m_pSequence = psd->midi_spec.sequence;
    }
#endif
#ifdef FEATURE_WEBAUDIO
    if (AEECLSID_MEDIAPMD ==  pme->m_clsSelf)
    {
      pme->m_pSequence = psd->wa_spec.sequence;
    }
#endif
#ifdef FEATURE_SMAF
    if (AEECLSID_MEDIAMMF == pme->m_clsSelf || AEECLSID_MEDIAPHR == pme->m_clsSelf)
    {
      pme->m_pSequence = psd->smaf_spec.sequence;
    }
#endif
#ifdef FEATURE_XMF
    if (AEECLSID_MEDIAXMF == pme->m_clsSelf)
    {
      // XMF media spec
    }
#endif
   }
#endif // defined(FEATURE_BREW_CMX_V3) 
}

/*==================================================================

==================================================================*/
#if defined(FEATURE_WEBAUDIO)
static void ProcessPMDText(void * pUser)
{
   CMediaPMD * pmdInfo;
   cmx_af_text_ctl_type * pTextCtl;
   CMediaBg *   pme = (CMediaBg *)pUser;

   if (!pme || !pme->u.m_pPMD)
      return;

   pmdInfo = (CMediaPMD *)pme->u.m_pPMD;
   pTextCtl = &pmdInfo->m_textCtl;

   // Copy the text info and schedule a resume. 
   // Pre-allocate static storage area fpr 2-3 text info and callbacks.
   // In the resume callback,
   // (1) Convert PMD text to HTML text
   // (2) Instantiate IHTMLViewer and display the text
   // TBD: Cost of using IHTMLViewer vs parsing and displaying here??
      if (!(pme->m_dwEnabledCaps & MM_CAPS_TEXT))
      return;

   // Only support ANSI right now
   if (pme->u.m_pPMD->m_char_set != CMX_AF_CHAR_SET_ANSI)
      return;

      // Switch on the different commands
   switch (pme->u.m_pPMD->m_textCtl.text_ctl)
   {
   // New text
   case CMX_AF_TEXT_CTL_NEW:
      {
      TextBlock * pBlock, * pOldBlock;
      pBlock=pmdInfo->m_textBlock;
      while (pBlock)
      {
         FREEIF(pBlock->m_text);
         pOldBlock = pBlock;
         pBlock = pOldBlock->m_next;
         FREEIF(pOldBlock);
      }
      pmdInfo->m_textBlock = NULL;
      pmdInfo->m_numToWipe = 0;
      pmdInfo->m_fTextDraw |= OEMMM_DISP_CLEAR;
      break;
      }
   // Append text
   case CMX_AF_TEXT_CTL_APPEND:
      pmdInfo->m_fTextDraw |= OEMMM_DISP_CLEAR;
      break;
   // Clear the text
   case CMX_AF_TEXT_CTL_CLEAR:
      {
      TextBlock * pBlock, * pOldBlock;
      pBlock=pmdInfo->m_textBlock;
      while (pBlock)
      {
         FREEIF(pBlock->m_text);
         pOldBlock = pBlock;
         pBlock = pOldBlock->m_next;
         FREEIF(pOldBlock);
      }
      pmdInfo->m_textBlock = NULL;
      pmdInfo->m_fTextDraw |= OEMMM_DISP_CLEAR;
      break;
      }
   // Advance the number of chars to wipe
   case CMX_AF_TEXT_CTL_ADVANCE:
      pmdInfo->m_numToWipe += (uint16)pTextCtl->num;
      break;
   // Rewind the number of chars to wipe
   case CMX_AF_TEXT_CTL_REWIND:
      pmdInfo->m_numToWipe -= (uint16)pTextCtl->num;
      break;
   case CMX_AF_TEXT_CTL_ENABLE:
      pmdInfo->m_fTextDraw = OEMMM_DISP_DRAW;
      break;
   case CMX_AF_TEXT_CTL_DISABLE:
      pmdInfo->m_fTextDraw &= ~OEMMM_DISP_DRAW;
      break;
   }

   if (pme->u.m_pPMD->m_textCtl.text_ctl == CMX_AF_TEXT_CTL_NEW ||
       pme->u.m_pPMD->m_textCtl.text_ctl == CMX_AF_TEXT_CTL_APPEND)
   {
      uint16 last, curr, end, row, ruby;
      TextBlock * pBlock, * pOldBlock;
      RGBVAL color, wipeColor;
      uint32 style;
      uint16 textXSize, textYSize;
      uint16 fontYSize;
      AEERect * pRect;

      last=curr=0;
      end = (uint16)pTextCtl->num;

      pBlock = pmdInfo->m_textBlock;
      while (pBlock && pBlock->m_next)
         pBlock = pBlock->m_next;

      if (!pBlock)
      {
         color = OEMMM_DEF_TEXT_COLOR;
         wipeColor = OEMMM_DEF_WIPE_COLOR;
         style = 0;
         row = 1;
         ruby=0;
      }
      else
      {
         color = pBlock->m_color;
         wipeColor = pBlock->m_wipeColor;
         style = pBlock->m_style;
         row   = pBlock->m_row;
         if (!pBlock->m_extent)
            ruby = 1;
         else
            ruby = 0;
      }

      while (curr < end)
      {

         while (pTextCtl->buffer[curr] != '<' &&
                pTextCtl->buffer[curr] != '&' &&
                curr < end) ++curr;

         if (curr-last)
         {
            if (pBlock)
            {
               pBlock->m_next = (TextBlock *)MALLOC(sizeof(TextBlock));
               if (!pBlock->m_next)
                  return;
               pBlock = pBlock->m_next;
            }
            else
            {
               pmdInfo->m_textBlock = (TextBlock *)MALLOC(sizeof(TextBlock));
               if (!pmdInfo->m_textBlock)
                  return;
               pBlock = pmdInfo->m_textBlock;
            }
            pBlock->m_text = MALLOC((curr-last+1)*sizeof(AECHAR));
            if (!pBlock->m_text)
               return;
            MEMSET(pBlock->m_text, 0, sizeof(AECHAR));
            UTF8TOWSTR(&pTextCtl->buffer[last], curr-last, pBlock->m_text, (curr-last)*sizeof(AECHAR));
            pBlock->m_length = curr-last;
            pBlock->m_color = color;
            pBlock->m_wipeColor = wipeColor;
            pBlock->m_style = style;
            pBlock->m_row = row;
            pBlock->m_next = NULL;
            pBlock->m_extent = IDISPLAY_MeasureText(pmdInfo->m_pIDisp,
               AEE_FONT_LARGE, pBlock->m_text);
         }

         if (curr >= end)
            break;

         if (pTextCtl->buffer[curr] == '<')
         {
            if (!STRNICMP((const char *)&pTextCtl->buffer[curr], "<BR>", 4))
               ++row;
            if (!STRNICMP((const char *)&pTextCtl->buffer[curr], "<P>", 3))
               row+=2;
            if (!STRNICMP((const char *)&pTextCtl->buffer[curr], "<FONT COLOR=\"#", 14))
            {
               char * end;
               unsigned long newColor;
               curr += 14;
               newColor = STRTOUL((const char *)&pTextCtl->buffer[curr], &end, 16);
               newColor = NTOHL(newColor);
               if (*end == '\"')
               {
                  color = newColor;
               }
            }
            if (!STRNICMP((const char *)&pTextCtl->buffer[curr], "<WIPE COLOR=\"#", 14))
            {
               char * end;
               unsigned long newColor;
               curr += 14;
               newColor = STRTOUL((const char *)&pTextCtl->buffer[curr], &end, 16);
               newColor = NTOHL(newColor);
               if (*end == '\"')
               {
                  wipeColor = newColor;
               }
            }
            if (!STRNICMP((const char *)&pTextCtl->buffer[curr], "<R \"", 3))
            {
                  ruby=1;

               while (pTextCtl->buffer[curr] != '\"' &&
                  curr < end) ++curr;
               ++curr;
               last = curr;
               while (pTextCtl->buffer[curr] != '\"' &&
                  curr < end) ++curr;

               if (curr-last)
               {
                  if (pBlock)
                  {
                     pBlock->m_next = (TextBlock *)MALLOC(sizeof(TextBlock));
                     if (!pBlock->m_next)
                        return;
                     pBlock = pBlock->m_next;
                  }
                  else
                  {
                     pmdInfo->m_textBlock = (TextBlock *)MALLOC(sizeof(TextBlock));
                     if (!pmdInfo->m_textBlock)
                        return;
                     pBlock = pmdInfo->m_textBlock;
                  }
                  pBlock->m_text = MALLOC((curr-last+1)*sizeof(AECHAR));
                  if (!pBlock->m_text)
                     return;
                  MEMSET(pBlock->m_text, 0, sizeof(AECHAR));
                  UTF8TOWSTR(&pTextCtl->buffer[last], curr-last, pBlock->m_text, (curr-last)*sizeof(AECHAR));
                  pBlock->m_length = curr-last;
                  pBlock->m_color = color;
                  pBlock->m_wipeColor = wipeColor;
                  pBlock->m_style = style;
                  pBlock->m_row = row;
                  pBlock->m_next = NULL;
                  pBlock->m_extent = 0;
               }
            }
            if (!STRNICMP((const char *)&pTextCtl->buffer[curr], "<U>", 3))
               style |= IDF_TEXT_UNDERLINE;
            if (!STRNICMP((const char *)&pTextCtl->buffer[curr], "</FONT>", 7))
               color = 0;
            if (!STRNICMP((const char *)&pTextCtl->buffer[curr], "</WIPE>", 7))
               wipeColor = OEMMM_DEF_WIPE_COLOR;
            if (!STRNICMP((const char *)&pTextCtl->buffer[curr], "</U>", 4))
               style &= ~IDF_TEXT_UNDERLINE;

            while (pTextCtl->buffer[curr++] != '>' &&
               curr < end);

            last = curr;
         }
         else if (pTextCtl->buffer[curr] == '&')
         {
            ++curr;
            if (!STRNCMP((const char *)&pTextCtl->buffer[curr], "#60;", 4))
            {
               pTextCtl->buffer[curr+3] = '<';
               curr+=4;
            }
            else if (!STRNCMP((const char *)&pTextCtl->buffer[curr], "#62;", 4))
            {
               pTextCtl->buffer[curr+3] = '>';
               curr+=4;
            }
            else if (!STRNCMP((const char *)&pTextCtl->buffer[curr], "#34;", 4))
            {
               pTextCtl->buffer[curr+3] = '\"';
               curr+=4;
            }
            else if (!STRNCMP((const char *)&pTextCtl->buffer[curr], "#38;", 4))
            {
               pTextCtl->buffer[curr+3] = '&';
               curr+=4;
            }
            last=curr - 1;
         }
      }

      pRect = &pmdInfo->m_rectImage;
      fontYSize = IDISPLAY_GetFontMetrics(pmdInfo->m_pIDisp, AEE_FONT_LARGE, NULL, NULL);
      if (ruby)
         ++row;
      textYSize = row * (fontYSize + 1) - 1;

      switch (pme->u.m_pPMD->m_textCtl.ypos_type)
      {
      case CMX_AF_POS_LT: textYSize = pRect->y; break;
      case CMX_AF_POS_RB: textYSize = pRect->y + abs(pRect->dy - textYSize); break;
      case CMX_AF_POS_CENTER: textYSize = pRect->y + abs(pRect->dy - textYSize)/2; break;
      default: textYSize = pRect->y + abs(pRect->dy - textYSize)/2; break;
      }

      for (row = 1, pBlock = pmdInfo->m_textBlock; pBlock; ++row)
      {
         pOldBlock = pBlock;
         textXSize = 0;
         while ((pBlock) &&
                (pBlock->m_row == row))
         {
            textXSize += pBlock->m_extent;
            pBlock = pBlock->m_next;
         }

         // Figure out the placement
         switch (pme->u.m_pPMD->m_textCtl.xpos_type)
         {
         case CMX_AF_POS_LT: textXSize = pRect->x; break;
         case CMX_AF_POS_RB: textXSize = pRect->x + abs(pRect->dx - textXSize); break;
         case CMX_AF_POS_CENTER: textXSize = pRect->x + abs(pRect->dx - textXSize)/2; break;
         default: textXSize = pRect->x + abs(pRect->dx - textXSize)/2; break;
         }

         while (pOldBlock != pBlock)
         {
            pOldBlock->m_x = textXSize;
            if (pOldBlock->m_extent)
               pOldBlock->m_y = textYSize + (pOldBlock->m_row - 1 + ruby)*(fontYSize + 1);
            else
               pOldBlock->m_y = textYSize + (pOldBlock->m_row - 2 + ruby)*(fontYSize + 1);
            textXSize += pOldBlock->m_extent;
            pOldBlock = pOldBlock->m_next;
         }
      }
   }

   AEE_ResumeCallback(&pme->u.m_pPMD->m_cbPMDImage, pme->m_pac);

   return;
}

/*==================================================================

==================================================================*/
static void CMXImage_Notify(void * pUser)
{
   CMediaBg *   pme = (CMediaBg *)pUser;

   if (!pme || !pme->u.m_pPMD || !pme->u.m_pPMD->m_pIDisp)
      return;

   if (pme->u.m_pPMD->m_pIDisp &&
       (pme->u.m_pPMD->m_fSafDraw & OEMMM_DISP_CLEAR ||
       pme->u.m_pPMD->m_fPicDraw & OEMMM_DISP_CLEAR ||
       pme->u.m_pPMD->m_fTextDraw & OEMMM_DISP_CLEAR))
   {
      IDISPLAY_EraseRect(pme->u.m_pPMD->m_pIDisp, &pme->u.m_pPMD->m_rectImage);
      if (pme->u.m_pPMD->m_pIDispBitmap == pme->u.m_pPMD->m_pIPicBitmap)
         pme->u.m_pPMD->m_fPicDraw &= ~OEMMM_DISP_CLEAR;
      if (pme->u.m_pPMD->m_pIDispBitmap == pme->u.m_pPMD->m_pISafBitmap)
         pme->u.m_pPMD->m_fSafDraw &= ~OEMMM_DISP_CLEAR;
      pme->u.m_pPMD->m_fTextDraw &= ~OEMMM_DISP_CLEAR;
   }

   // Picture
   if (pme->u.m_pPMD->m_pIDispBitmap)
   {
      AEERect *pRect = &(pme->u.m_pPMD->m_rectImage);
         uint16 width, height;

      if(pme->u.m_pPMD->m_pIDispBitmap == pme->u.m_pPMD->m_pIPicBitmap ||
         pme->u.m_pPMD->m_pIDispBitmap == pme->u.m_pPMD->m_pISafBitmap)

      {
         if (pme->u.m_pPMD->m_fPicDraw & OEMMM_DISP_DRAW)
         {

            // Copy the picture to the display if one has ever been displayed.
            if (pme->u.m_pPMD->m_displayedPic == TRUE) {
                if (pme->u.m_pPMD->m_picDispRect.x + pme->u.m_pPMD->m_picDispRect.dx
                    > pRect->x + pRect->dx)
                   width = pRect->x + pRect->dx - pme->u.m_pPMD->m_picDispRect.x;
                else
                   width = pme->u.m_pPMD->m_picDispRect.dx;

                if (pme->u.m_pPMD->m_picDispRect.y + pme->u.m_pPMD->m_picDispRect.dy 
                    > pRect->y + pRect->dy)
                   height = pRect->y + pRect->dy - pme->u.m_pPMD->m_picDispRect.y;
                else
                   height = pme->u.m_pPMD->m_picDispRect.dy;

                if (pme->u.m_pPMD->m_pIPicBitmap)
                  IDISPLAY_BitBlt(pme->u.m_pPMD->m_pIDisp, 
                                    pme->u.m_pPMD->m_picDispRect.x, 
                                    pme->u.m_pPMD->m_picDispRect.y, 
                                    width, 
                                    height, 
                                    (const void *) pme->u.m_pPMD->m_pIPicBitmap, 
                                    pme->u.m_pPMD->m_picOffsetRect.x, 
                                    pme->u.m_pPMD->m_picOffsetRect.y, 
                                    AEE_RO_COPY);
                                    //AEE_RO_TRANSPARENT);
            }
         }
         
         if (pme->u.m_pPMD->m_fSafDraw & OEMMM_DISP_DRAW)
         {

            if (pme->u.m_pPMD->m_displayedSaf == TRUE) {
                if (pme->u.m_pPMD->m_safDispRect.x + pme->u.m_pPMD->m_safDispRect.dx
                    > pRect->x + pRect->dx)
                   width = pRect->x + pRect->dx - pme->u.m_pPMD->m_safDispRect.x;
         else
                   width = pme->u.m_pPMD->m_safDispRect.dx;

                if (pme->u.m_pPMD->m_safDispRect.y + pme->u.m_pPMD->m_safDispRect.dy 
                    > pRect->y + pRect->dy)
                   height = pRect->y + pRect->dy - pme->u.m_pPMD->m_safDispRect.y;
         else
                   height = pme->u.m_pPMD->m_safDispRect.dy;

                if (pme->u.m_pPMD->m_pISafBitmap) {

                   IDISPLAY_BitBlt(pme->u.m_pPMD->m_pIDisp, 
                           pme->u.m_pPMD->m_safDispRect.x, 
                           pme->u.m_pPMD->m_safDispRect.y, 
                           width, 
                           height, 
                           (const void *) pme->u.m_pPMD->m_pISafBitmap, 
                           pme->u.m_pPMD->m_safOffsetRect.x, 
                           pme->u.m_pPMD->m_safOffsetRect.y, 
                           pme->u.m_pPMD->m_transparency ? AEE_RO_TRANSPARENT : AEE_RO_COPY);
                }
            }
         }
      }
   }

   // Text
   if (pme->u.m_pPMD->m_textBlock)
   {
      // Process and display PMD text
      CMediaPMD * pPmd = (CMediaPMD *)pme->u.m_pPMD;

      if(pme->u.m_pPMD->m_fTextDraw & OEMMM_DISP_DRAW)
      {
         uint16 numToWipe = pPmd->m_numToWipe;
         TextBlock * pBlock = pPmd->m_textBlock;

         while (pBlock)
         {
            RGBVAL oldColor;
            oldColor = IDISPLAY_SetColor(pPmd->m_pIDisp, CLR_USER_TEXT, pBlock->m_color);
            IDISPLAY_DrawText(pPmd->m_pIDisp, AEE_FONT_LARGE,
               pBlock->m_text, pBlock->m_length,
               pBlock->m_x, pBlock->m_y,
               &pPmd->m_rectImage, IDF_TEXT_TRANSPARENT | pBlock->m_style);
            IDISPLAY_SetColor(pPmd->m_pIDisp, CLR_USER_TEXT, oldColor);

            if (numToWipe > 0)
            {
               RGBVAL lastColor;
               uint16 wipeNum;
               lastColor = IDISPLAY_SetColor(pPmd->m_pIDisp, CLR_USER_TEXT, pBlock->m_wipeColor);
               if (pBlock->m_length < numToWipe)
                  wipeNum = pBlock->m_length;
               else
                  wipeNum = numToWipe;
               IDISPLAY_DrawText(pPmd->m_pIDisp, AEE_FONT_LARGE,
                  pBlock->m_text, wipeNum,
                  pBlock->m_x, pBlock->m_y,
                  &pPmd->m_rectImage, IDF_TEXT_TRANSPARENT | pBlock->m_style);

               // If this is Ruby also wipe the bottom part
               if (!pBlock->m_extent && pBlock->m_next)
               {
                  uint16 rubyWipeNum;
                  pBlock = pBlock->m_next;
                  IDISPLAY_SetColor(pPmd->m_pIDisp, CLR_USER_TEXT, pBlock->m_color);
                  IDISPLAY_DrawText(pPmd->m_pIDisp, AEE_FONT_LARGE,
                     pBlock->m_text, pBlock->m_length,
                     pBlock->m_x, pBlock->m_y,
                     &pPmd->m_rectImage, IDF_TEXT_TRANSPARENT | pBlock->m_style);
                  IDISPLAY_SetColor(pPmd->m_pIDisp, CLR_USER_TEXT, pBlock->m_wipeColor);
                  if (pBlock->m_length < numToWipe)
                     rubyWipeNum = pBlock->m_length;
                  else
                     rubyWipeNum = numToWipe;
                  IDISPLAY_DrawText(pPmd->m_pIDisp, AEE_FONT_LARGE,
                     pBlock->m_text, rubyWipeNum,
                     pBlock->m_x, pBlock->m_y,
                     &pPmd->m_rectImage, IDF_TEXT_TRANSPARENT | pBlock->m_style);
                  if (rubyWipeNum > wipeNum)
                     wipeNum = rubyWipeNum;
               }
               
               lastColor = IDISPLAY_SetColor(pPmd->m_pIDisp, CLR_USER_TEXT, lastColor);
               numToWipe -= wipeNum;
            }
            pBlock = pBlock->m_next;
         }
      }
   }

   IDISPLAY_Update(pme->u.m_pPMD->m_pIDisp);

   //PMDPlayerDisplay(gsBuffer, gsdwSize, pRect->x, pRect->y, pRect->dx, pRect->dy);
}

/*==================================================================

==================================================================*/
void CMXImage_Process(CMediaBg * pme, cmx_status_type status, cmx_af_server_data_type * psd)
{
   boolean  bDispatch = FALSE;

   if (!psd)
      return;

   if (!pme->u.m_pPMD)
      return;

   if (!(pme->m_dwEnabledCaps & MM_CAPS_VIDEO))
      return;

   switch (status)
   {
      case CMX_PICTURE_STATUS:
         switch(psd->pic_status.pic_status) {
            case CMX_AF_PIC_STATUS_INIT:
               pme->u.m_pPMD->m_fPicDraw |= OEMMM_DISP_CLEAR;  // Set CLEAR flag, leave prev. DRAW flag
               //Copy server data and queue callback in user mode
               pme->u.m_pPMD->m_picStatus = psd->pic_status;
               AEE_ResumeCallback(&pme->u.m_pPMD->m_cbPicInitialize, pme->m_pac);
               break;
            case CMX_AF_PIC_STATUS_CLEAR:
               pme->u.m_pPMD->m_fPicDraw |= OEMMM_DISP_CLEAR;  // Set CLEAR flag, leave prev. DRAW flag
               bDispatch = TRUE;
               AEE_ResumeCallback(&pme->u.m_pPMD->m_cbPicRelease, pme->m_pac);
               break;
            case CMX_AF_PIC_STATUS_ENABLE:
               pme->u.m_pPMD->m_fPicDraw |= OEMMM_DISP_DRAW;   // Set DRAW flag, leave prev. CLEAR flag
               bDispatch = TRUE;               
               break;
            case CMX_AF_PIC_STATUS_DISABLE:
               pme->u.m_pPMD->m_fPicDraw = OEMMM_DISP_CLEAR;   // Set CLEAR flag, clear DRAW flag
               bDispatch = TRUE;               
               break;
            default:
               break;
         }
         break;

      case CMX_PICTURE_DATA:
         // Make a shallow copy of the data passed (buffer is one of m_ppClientBufs)
         // and queue callback in user mode
         ((CMediaBg*)pme)->u.m_pPMD->m_picData = psd->pic_data;
            
         switch(psd->pic_data.pic_type) {
            case CMX_AF_PIC_BMP:
               //process BMP only if we have all the data
               if(psd->pic_data.pic_data == CMX_AF_PIC_DATA_DONE) {
                  AEE_ResumeCallback(&((CMediaBg*)pme)->u.m_pPMD->m_cbCopyBMPBuf, pme->m_pac);
               }
               break;

            case CMX_AF_PIC_PNG:
               AEE_ResumeCallback(&((CMediaBg*)pme)->u.m_pPMD->m_cbCopyPNGBuf, pme->m_pac);
               break;
         

#ifdef FEATURE_AUDIO_POSTCARD
            case CMX_AF_PIC_JPEG:
               AEE_ResumeCallback(&((CMediaBg*)pme)->u.m_pPMD->m_cbCopyJPEGBuf, pme->m_pac);
               break;
#endif /* FEATURE_AUDIO_POSTCARD */

            default:
               break;
         }
         break;

#if defined(FEATURE_CMX_CLIENT_BUF_ALLOC)        
      case CMX_CLIENT_BUF_FREE:
         pme->u.m_pPMD->m_ClientBufSent = psd->client_buf_free;
         AEE_ResumeCallback(&pme->u.m_pPMD->m_cbClientBufFree, pme->m_pac);
         break;
 
      case CMX_CLIENT_BUF_REQ:
         pme->u.m_pPMD->m_ClientBufSize = psd->client_buf_req.buffer_size;
         pme->u.m_pPMD->m_cbClientBufAllocNotify = psd->client_buf_req.cb_func;
         pme->u.m_pPMD->m_pBufClientData = psd->client_buf_req.client_data;
         psd->client_buf_req.buffer_supplied = TRUE;

         AEE_ResumeCallback(&pme->u.m_pPMD->m_cbClientBufAlloc, pme->m_pac);
         break;
#endif // defined(FEATURE_CMX_CLIENT_BUF_ALLOC)        

#if defined(FEATURE_BREW_CMX_V3)
      case CMX_GET_BUFFER:
#if !defined(FEATURE_CMX_CLIENT_BUF_ALLOC)
         //Copy the callback data and queue the allocation to happen in user mode
         pme->u.m_pPMD->m_pJPEGBufSize = psd->client_buf_req.buffer_size;
         pme->u.m_pPMD->m_cbJPEGBufAllocNotify = psd->client_buf_req.cb_func;
         pme->u.m_pPMD->m_pJPEGClientData = psd->client_buf_req.client_data;

         AEE_ResumeCallback(&pme->u.m_pPMD->m_cbJPEGBufAlloc, pme->m_pac);
/*
#else
         if (pme->u.m_pPMD->m_pJPEGBuf)
             FREE(pme->u.m_pPMD->m_pJPEGBuf);
         pme->u.m_pPMD->m_pJPEGBuf = (byte *)MALLOC(
                                             psd->client_buf_req.buffer_size);
         pme->u.m_pPMD->m_pJPEGBufSize = psd->client_buf_req.buffer_size;
         if(psd->client_buf_req.cb_func != NULL) {
             psd->client_buf_req.cb_func(pme->u.m_pPMD->m_pJPEGBuf,
                                      psd->client_buf_req.client_data);
         }
*/
#endif // !defined(FEATURE_CMX_CLIENT_BUF_ALLOC)
         break;
#endif // defined(FEATURE_BREW_CMX_V3)

      case CMX_SAF_INFO:
         //this is where all the SAF frame stuff comes

         //Copy server data and queue callback in user mode
         pme->u.m_pPMD->m_safInfo = psd->saf_info;
         AEE_ResumeCallback(&pme->u.m_pPMD->m_cbSAFCreateDIB, pme->m_pac);         
         break;

      case CMX_SAF_FRAME:
         // Make a shallow copy of the data passed (buffer is one of m_ppClientBufs)
         // and queue callback in user mode
         pme->u.m_pPMD->m_safData = psd->saf_data;  
         AEE_ResumeCallback(&pme->u.m_pPMD->m_cbSAFLoadFrame, pme->m_pac);         
         break;

      case CMX_ANIM_CLEAR:
         pme->u.m_pPMD->m_fSafDraw |= OEMMM_DISP_CLEAR;  // Set CLEAR flag, leave prev. DRAW flag
         pme->u.m_pPMD->m_displayedSaf = FALSE;
            bDispatch = TRUE;
         break;

      case CMX_ANIM_ENABLE:
         pme->u.m_pPMD->m_fSafDraw |= OEMMM_DISP_DRAW;   // Set DRAW flag, leave prev. CLEAR flag
         bDispatch = TRUE; 
         break;

      case CMX_ANIM_DISABLE:
         pme->u.m_pPMD->m_fSafDraw = OEMMM_DISP_CLEAR;   // Set CLEAR flag, clear DRAW flag
         bDispatch = TRUE; 
         break;

      default:
         break;
 
   }

   if(bDispatch) {
      //Update screen
      AEE_ResumeCallback(&pme->u.m_pPMD->m_cbPMDImage, pme->m_pac);
   }

}

/*==================================================================

==================================================================*/
static void PICInitialize(void * pUser) {
   CMediaBg *   pme = (CMediaBg *)pUser;

   if (!pme || !pme->u.m_pPMD)
      return;

   //If the OEM has constants to use for display height and width,
   //they can use it here instead of doing the GetDeviceInfo stuff
   {
      AEEDeviceInfo  info;

      ISHELL_GetDeviceInfo(pme->m_pIShell, &info);
      pme->u.m_pPMD->m_cxScreen = info.cxScreen;
      pme->u.m_pPMD->m_cyScreen = info.cyScreen;

      if(info.nColorDepth <= 8)
         pme->u.m_pPMD->m_clrDepth = 8;
      else
         pme->u.m_pPMD->m_clrDepth = 16;
   }

   pme->u.m_pPMD->m_fJust = 0;
   FREEIF(pme->u.m_pPMD->m_pPNGBuf);
   pme->u.m_pPMD->m_pPNGBufSize = 0;

   switch(pme->u.m_pPMD->m_picStatus.xpos_type) {
      case CMX_AF_POS_LT:
         pme->u.m_pPMD->m_fJust |= OEMMM_JUST_LEFT;
         break;
      case CMX_AF_POS_RB:
         pme->u.m_pPMD->m_fJust |= OEMMM_JUST_RIGHT;
         break;
      case CMX_AF_POS_CENTER:
         pme->u.m_pPMD->m_fJust |= OEMMM_JUST_XCENTR;
         break;
      case CMX_AF_POS_PERCENT:
         pme->u.m_pPMD->m_fJust |= OEMMM_JUST_XPCENT;
         pme->u.m_pPMD->m_xJustPcent_Pixel = pme->u.m_pPMD->m_picStatus.xpos_percent;
         break;
      case CMX_AF_POS_PIXEL:
         pme->u.m_pPMD->m_fJust |= OEMMM_JUST_XPIXEL;
         pme->u.m_pPMD->m_xJustPcent_Pixel = pme->u.m_pPMD->m_picStatus.xpos_percent;
         break;
   }
   switch(pme->u.m_pPMD->m_picStatus.ypos_type) {
      case CMX_AF_POS_LT:
         pme->u.m_pPMD->m_fJust |= OEMMM_JUST_TOP;
         break;
      case CMX_AF_POS_RB:
         pme->u.m_pPMD->m_fJust |= OEMMM_JUST_BOT;
         break;
      case CMX_AF_POS_CENTER:
         pme->u.m_pPMD->m_fJust |= OEMMM_JUST_YCENTR;
         break;
      case CMX_AF_POS_PERCENT:
         pme->u.m_pPMD->m_fJust |= OEMMM_JUST_YPCENT;
         pme->u.m_pPMD->m_yJustPcent_Pixel = pme->u.m_pPMD->m_picStatus.ypos_percent;
         break;
      case CMX_AF_POS_PIXEL:
         pme->u.m_pPMD->m_fJust |= OEMMM_JUST_YPIXEL;
         pme->u.m_pPMD->m_yJustPcent_Pixel = pme->u.m_pPMD->m_picStatus.ypos_percent;
         break;
   }
   
   pme->u.m_pPMD->m_bHeaderRead = FALSE;
      
}

static void PICRelease(void * pUser) {

   CMediaBg *   pme = (CMediaBg *)pUser;

   if (!pme || !pme->u.m_pPMD)
      return;

   if(pme->u.m_pPMD->m_pIPicBitmap) {
      if(pme->u.m_pPMD->m_pIPicBitmap->cntRGB)
         FREEIF(pme->u.m_pPMD->m_pIPicBitmap->pRGB);
      IDIB_Release(pme->u.m_pPMD->m_pIPicBitmap);
      pme->u.m_pPMD->m_pIPicBitmap = NULL;
   }
}

/*==================================================================

==================================================================*/
static void BMPCopyBuffer(void * pUser) {
   
   CMediaBg *   pme = (CMediaBg *)pUser;
   WinBMPInfoHdr *pBMPHdr;
   IDShift * pIDShift;
   int       i;
   int       height;

   if (!pme || !pme->u.m_pPMD)
      return;

   //The BMP starts with the BMP Info header.
   pBMPHdr = (WinBMPInfoHdr *) pme->u.m_pPMD->m_picData.buffer;

   if(pBMPHdr->wBitsPerPix != 24)
      return;

     height = pBMPHdr->dwBIHeight;

   if(height < 0)
      height = -height;


   if(pme->u.m_pPMD->m_pIPicBitmap) {
      if(pme->u.m_pPMD->m_pIPicBitmap->cntRGB && pme->u.m_pPMD->m_pIPicBitmap->pRGB) 
         FREE(pme->u.m_pPMD->m_pIPicBitmap->pRGB);
      IDIB_Release(pme->u.m_pPMD->m_pIPicBitmap);
   }


   IDISPLAY_CreateDIBitmap(pme->u.m_pPMD->m_pIDisp, 
                           (IDIB**)&pme->u.m_pPMD->m_pIPicBitmap, 
                           (uint8) pme->u.m_pPMD->m_clrDepth, 
                           (uint8) pBMPHdr->dwBIWidth,
                           (uint8) height
                           );

   if(pme->u.m_pPMD->m_pIPicBitmap) {
 
      if(pme->u.m_pPMD->m_clrDepth == 16)
         pme->u.m_pPMD->m_pIPicBitmap->nColorScheme = IDIB_COLORSCHEME_565;
      else
         pme->u.m_pPMD->m_pIPicBitmap->nColorScheme = IDIB_COLORSCHEME_332;
      
      IDShift_New(pme->m_pIShell, (void**) &pIDShift);

      if(pIDShift) {
         AEEDShift dshift;
         int       rowWidth = (pBMPHdr->dwBIWidth * 3 + 3)&~3; 

         byte     *pSrc = (byte *) pme->u.m_pPMD->m_picData.buffer + sizeof(WinBMPInfoHdr);

         dshift.bDither = FALSE;
         dshift.nDstDepth = (uint8) pme->u.m_pPMD->m_clrDepth;
         dshift.nSrcDepth = 24;
         dshift.nWidth  = (uint16) pBMPHdr->dwBIWidth;

         IDShift_Configure(pIDShift, &dshift);

         if(pBMPHdr->dwBIHeight > 0) {

            for(i = 0; i < height; i++) 
            
               IDShift_Convert(pIDShift, 
                               ((byte *)pme->u.m_pPMD->m_pIPicBitmap->pBmp) + i* pme->u.m_pPMD->m_pIPicBitmap->nPitch, 
                               pSrc + (height - i - 1)*rowWidth, TRUE
                              );
         }
         else {
            for(i = 0; i < height; i++) 
            
               IDShift_Convert(pIDShift, 
                               ((byte *)pme->u.m_pPMD->m_pIPicBitmap->pBmp) + i * pme->u.m_pPMD->m_pIPicBitmap->nPitch, 
                               pSrc + i*rowWidth, TRUE
                              );
         }

         pme->u.m_pPMD->m_dispRect.dx = (uint16) pBMPHdr->dwBIWidth;
         pme->u.m_pPMD->m_dispRect.dy = (uint16) height;
         

         IDShift_Delete(pIDShift);
           
         pme->u.m_pPMD->m_pIDispBitmap = pme->u.m_pPMD->m_pIPicBitmap;
         SetImageOffsets(pme);
         AEE_ResumeCallback(&pme->u.m_pPMD->m_cbPMDImage, pme->m_pac);
      }
   }
   return;
}

/*==================================================================

==================================================================*/
static void PNGCopyBuffer(void * pUser)
{

   CMediaBg *   pme = (CMediaBg *)pUser;
   uint32 oldSize;
   uint32 colorType, width, height, depth, colorMapSize;
   uint32 index = 0;
   uint8 lines = 3;
   uint8 bbp;
   byte * colorMap = NULL;
   IDIB * pDib = NULL;
   byte * pNewBuf;

   if (!pme || !pme->u.m_pPMD)
      return;

   // Ignore the text section
   if (pme->u.m_pPMD->m_picData.pic_data == CMX_AF_PIC_DATA_TEXT)
      return;

   // Copy out the data and append
   oldSize = pme->u.m_pPMD->m_pPNGBufSize;
   pme->u.m_pPMD->m_pPNGBufSize += pme->u.m_pPMD->m_picData.num;

   pNewBuf = (byte *)REALLOC(pme->u.m_pPMD->m_pPNGBuf, pme->u.m_pPMD->m_pPNGBufSize);
   if (pNewBuf)
   {
      pme->u.m_pPMD->m_pPNGBuf = pNewBuf;
   }
   else
   {
      return;
   }

   MEMCPY(pme->u.m_pPMD->m_pPNGBuf + oldSize, pme->u.m_pPMD->m_picData.buffer, pme->u.m_pPMD->m_picData.num);

   // If this is an append return, otherwise we're done so process it
   if (pme->u.m_pPMD->m_picData.pic_data == CMX_AF_PIC_DATA_APPEND)
      return;

   // Read the header
   if (sscanf((const char *)pme->u.m_pPMD->m_pPNGBuf, "%ld\n%ld %ld\n%ld\n", &colorType, &width, &height, &depth) != 4)
      return;

   while (lines--)
   {
      while (pme->u.m_pPMD->m_pPNGBuf[index++] != 0x0a)
         if (index > pme->u.m_pPMD->m_pPNGBufSize)
            return;
   }

   do
   {
      // Check for subchunks
      if (!strncmp((const char *)&pme->u.m_pPMD->m_pPNGBuf[index], "tran", 4))
      {
         // transparent chunk
         uint8 size;
         index += 5;
         if(sscanf((const char *)&pme->u.m_pPMD->m_pPNGBuf[index], "%d", &size) != 1)
            return;
         while (pme->u.m_pPMD->m_pPNGBuf[index++] != 0x0a)
            if (index > pme->u.m_pPMD->m_pPNGBufSize)
               return;
         index += size;
         continue;
      }
      if (!strncmp((const char *)&pme->u.m_pPMD->m_pPNGBuf[index], "bkgd", 4))
      {
         // background chunk
         while (pme->u.m_pPMD->m_pPNGBuf[index++] != 0x0a)
            if (index > pme->u.m_pPMD->m_pPNGBufSize)
               return;
         continue;
      }
      if (!strncmp((const char *)&pme->u.m_pPMD->m_pPNGBuf[index], "plte", 4))
      {
         // pallete chunk
         index += 5;
         if (sscanf((const char *)&pme->u.m_pPMD->m_pPNGBuf[index], "%d", &colorMapSize) != 1)
            return;
         while (pme->u.m_pPMD->m_pPNGBuf[index++] != 0x0a)
            if (index > pme->u.m_pPMD->m_pPNGBufSize)
               return;
         colorMap = (byte *)MALLOC(colorMapSize);
         if (colorMap)
         MEMCPY(colorMap, &pme->u.m_pPMD->m_pPNGBuf[index], colorMapSize);
         index += colorMapSize;
         continue;
      }
      // Done
      break;
   } while (1);

   // Get the bytes per pixel
   switch (colorType)
   {
   case 0: bbp = 1; break;
   case 2: bbp = 3; break;
   case 3: bbp = 1; break;
   case 4: bbp = 2; break;
   case 6: bbp = 4; break;
   default: return;
   }

   // Create the DIB
   if(pme->u.m_pPMD->m_pIPicBitmap) {
      if(pme->u.m_pPMD->m_pIPicBitmap->cntRGB && pme->u.m_pPMD->m_pIPicBitmap->pRGB) 
         FREE(pme->u.m_pPMD->m_pIPicBitmap->pRGB);
      IDIB_Release(pme->u.m_pPMD->m_pIPicBitmap);
   }

   if (IDISPLAY_CreateDIBitmap(pme->u.m_pPMD->m_pIDisp,
                               (IDIB**)&pme->u.m_pPMD->m_pIPicBitmap,
                               (uint8) depth,
                               (uint8) width,
                               (uint8) height) != SUCCESS)
      return;

   if(!pme->u.m_pPMD->m_pIPicBitmap)
      return;

   pDib = (IDIB *)pme->u.m_pPMD->m_pIPicBitmap;
   pDib->nPitch = (uint16)width*bbp;

   if (colorType == 3)
   {
      pDib->nColorScheme = 0;
   }
   else if(colorType == 2 || colorType == 6)
   {
      switch (depth)
      {
      case 8: pDib->nColorScheme = IDIB_COLORSCHEME_332; break;
      case 12: pDib->nColorScheme = IDIB_COLORSCHEME_444; break;
      case 15: pDib->nColorScheme = IDIB_COLORSCHEME_555; break;
      case 16: pDib->nColorScheme = IDIB_COLORSCHEME_565; break;
      case 24: pDib->nColorScheme = IDIB_COLORSCHEME_888; break;
      default: return;
      }
   }
   else
   {
      return;
   }

   if (colorType == 2 || colorType == 3)
   {
      MEMCPY(pDib->pBmp, &pme->u.m_pPMD->m_pPNGBuf[index], width*height*depth/8*bbp);
   }
   else if (colorType == 6)
   {
      // Skip the alpha values
      byte * p = pDib->pBmp;
      int i = index;
      int end = width*height*bbp;
      while (i < end)
      {
         *p++ = pme->u.m_pPMD->m_pPNGBuf[i++];
         *p++ = pme->u.m_pPMD->m_pPNGBuf[i++];
         *p++ = pme->u.m_pPMD->m_pPNGBuf[i++];
         i++;
      }
   }

   if (colorMap)
   {
      int i;
      int end = colorMapSize/3;
      uint32 * pPal;
      byte * pSAFPal;
      pDib->cntRGB = (uint16)colorMapSize/3;
      pDib->pRGB = (uint32 *)MALLOC(colorMapSize/3*4);

      if (pDib->pRGB)
      {
         pPal = pDib->pRGB;
         pSAFPal = colorMap;
         for(i = 0; i < end; i++) {
            *pPal = (uint32) (*pSAFPal << 16) ;
            pSAFPal++;
            *pPal |= (uint32) (*pSAFPal << 8);
            pSAFPal++;
            *pPal |= (uint32) *pSAFPal;
            pSAFPal++;
            pPal++;
         }
      }   
   }

   pme->u.m_pPMD->m_dispRect.dx = (uint16)width;
   pme->u.m_pPMD->m_dispRect.dy = (uint16)height;
      
   FREE(colorMap);
   FREE(pme->u.m_pPMD->m_pPNGBuf);
   pme->u.m_pPMD->m_pPNGBuf = NULL;
   pme->u.m_pPMD->m_pPNGBufSize = 0;

   if(pme->u.m_pPMD->m_picData.pic_data == CMX_AF_PIC_DATA_DONE) {
      pme->u.m_pPMD->m_pIDispBitmap = pme->u.m_pPMD->m_pIPicBitmap;
      SetImageOffsets(pme);
      AEE_ResumeCallback(&pme->u.m_pPMD->m_cbPMDImage, pme->m_pac);
   }
   return;
}

#ifdef FEATURE_AUDIO_POSTCARD
static void JPEGCopyBuffer(void * pUser) {
   CMediaBg *   pme = (CMediaBg *)pUser;
   byte * pDest;
   byte clr;
   uint32  i, end;
   boolean bReadHeader;

   if (!pme || !pme->u.m_pPMD)
      return;

   // Figure out what to do based on combination of 
   // pme->u.m_pPMD->m_picData.pic_data and pme->u.m_pPMD->m_bHeaderRead

   if(!pme->u.m_pPMD->m_bHeaderRead && pme->u.m_pPMD->m_picData.pic_data == CMX_AF_PIC_DATA_DONE)
      // We are getting data but haven't had a header
      return;
      
   if (pme->u.m_pPMD->m_picData.pic_data == CMX_AF_PIC_DATA_APPEND)
      bReadHeader = TRUE;
   else
      bReadHeader = FALSE;

   if(bReadHeader) {

      int32     width, height, max_pixel_value;
      char *   pszHeader = (char *)pme->u.m_pPMD->m_picData.buffer;
      char *   psz;
      
      if (!pszHeader)
         return;
         
      psz = STRSTR(pszHeader, "P6");
   
      pme->u.m_pPMD->m_bHeaderRead = TRUE;

      if (!psz)
         return;
      
      //Get to the end of "P6"   
      psz += 2;
         
      if (!*psz)
         return;
    
      /* Header is in the first chunk of data sent */
      sscanf( psz, 
              "%ld%ld\n%ld\n",
              &width,
              &height,
              &max_pixel_value );

      pme->u.m_pPMD->m_dispRect.dx = (int16)width;
      pme->u.m_pPMD->m_dispRect.dy = (int16)height;

      if(pme->u.m_pPMD->m_pIPicBitmap) {
         if(pme->u.m_pPMD->m_pIPicBitmap->cntRGB && pme->u.m_pPMD->m_pIPicBitmap->pRGB) 
            FREE(pme->u.m_pPMD->m_pIPicBitmap->pRGB);
         IDIB_Release(pme->u.m_pPMD->m_pIPicBitmap);
      }


      IDISPLAY_CreateDIBitmap(pme->u.m_pPMD->m_pIDisp, 
                              (IDIB**)&pme->u.m_pPMD->m_pIPicBitmap, 
                              (uint8) 24, 
                              (int16) width,
                              (int16) height
                              );

      if(pme->u.m_pPMD->m_pIPicBitmap) {

         //We can set the pitch correctly here
         pme->u.m_pPMD->m_pIPicBitmap->nPitch = (uint16) width * 3;
         pme->u.m_pPMD->m_pIPicBitmap->nColorScheme = IDIB_COLORSCHEME_888;
         goto Done;
      }
      else
         return;
   }

   if (!pme->u.m_pPMD->m_pIPicBitmap || !pme->u.m_pPMD->m_pIPicBitmap->pBmp)
      return;

   pDest = (byte *)pme->u.m_pPMD->m_pIPicBitmap->pBmp;   
   end = pme->u.m_pPMD->m_dispRect.dx * pme->u.m_pPMD->m_dispRect.dy;

   MEMCPY(pDest,
          pme->u.m_pPMD->m_pJPEGBuf,
          end*3
         );

   for(i = 0; i < end; i++) {
     clr        = *pDest;
     *pDest     = *(pDest + 2);
     *(pDest+2) = clr;
     pDest += 3;
   }

   // Queue request to free JPEG buffer in user context
   AEE_ResumeCallback(&pme->u.m_pPMD->m_cbJPEGBufFree, pme->m_pac);

   goto Done;
   
Done:
   if(pme->u.m_pPMD->m_picData.pic_data == CMX_AF_PIC_DATA_DONE)
   {
      pme->u.m_pPMD->m_bHeaderRead = FALSE;
      pme->u.m_pPMD->m_pIDispBitmap = pme->u.m_pPMD->m_pIPicBitmap;
      SetImageOffsets(pme);
      // Update the screen
      AEE_ResumeCallback(&pme->u.m_pPMD->m_cbPMDImage, pme->m_pac);
   }
}
#endif /* FEATURE_AUDIO_POSTCARD */

/*==================================================================

==================================================================*/
static void SAFCreateDIB(void * pUser) {
   CMediaBg *   pme = (CMediaBg *)pUser;
   int       numColors;
   uint8     width;
   uint8     height;
   uint32    *pPal;
   uint8     *pSAFPal;
   byte      *pTransp;
   uint32    i;
//   IDIB **   pBmp = pme->u.m_pPMD->m_pISafBitmap;

   if (!pme || !pme->u.m_pPMD)
      return;

   width  = (uint8)pme->u.m_pPMD->m_safInfo.width;
   height = (uint8)pme->u.m_pPMD->m_safInfo.height;
    
   if(pme->u.m_pPMD->m_pISafBitmap) {
      if(pme->u.m_pPMD->m_pISafBitmap->cntRGB && pme->u.m_pPMD->m_pISafBitmap->pRGB) 
         FREE(pme->u.m_pPMD->m_pISafBitmap->pRGB);
      IDIB_Release(pme->u.m_pPMD->m_pISafBitmap);
   }

/**
   if(pme->u.m_pPMD->m_safInfo.width < OEMMM_SAF_FRAME_MAXWIDTH)
      width = (uint8) pme->u.m_pPMD->m_safInfo.width;
   if(pme->u.m_pPMD->m_safInfo.height < OEMMM_SAF_FRAME_MAXHEIGHT)
      height = (uint8) pme->u.m_pPMD->m_safInfo.height;
**/
   IDISPLAY_CreateDIBitmap(pme->u.m_pPMD->m_pIDisp, 
                           (IDIB **)&pme->u.m_pPMD->m_pISafBitmap, 
                           (uint8) pme->u.m_pPMD->m_safInfo.bitdepth, 
                           width,
                           height
                           );
   i = sizeof(int);
   if(i > 4)
      return;

   if(pme->u.m_pPMD->m_pISafBitmap) {
      IBitmap *pBitmap = IDIB_TO_IBITMAP (pme->u.m_pPMD->m_pISafBitmap);

      numColors = pme->u.m_pPMD->m_safInfo.num_palette_entry;
      pme->u.m_pPMD->m_pISafBitmap->nPitch = (int16) (width * pme->u.m_pPMD->m_safInfo.bitdepth + 7)/8;
      if(numColors){

         //Get bit depth
         int tempNumColors = numColors;
         for (pme->u.m_pPMD->m_SAFBitDepth=0; tempNumColors>1; ++pme->u.m_pPMD->m_SAFBitDepth)
            tempNumColors >>= 1;
      
         pme->u.m_pPMD->m_pISafBitmap->pRGB = MALLOC(numColors * 4);
         if(pme->u.m_pPMD->m_pISafBitmap->pRGB) {
            pPal = pme->u.m_pPMD->m_pISafBitmap->pRGB;
            pSAFPal = pme->u.m_pPMD->m_safInfo.palette;
            for(i = 0; i < pme->u.m_pPMD->m_safInfo.num_palette_entry; i++) {
               *pPal = (uint32) (*pSAFPal << 16) ;
               pSAFPal++;
               *pPal |= (uint32) (*pSAFPal << 8);
               pSAFPal++;
               *pPal |= (uint32) *pSAFPal;
               pSAFPal++;
               pPal++;
            }
            pme->u.m_pPMD->m_pISafBitmap->cntRGB = numColors;
            pTransp = pme->u.m_pPMD->m_safInfo.global_trans;
    
            //for(i = 0; i < pme->u.m_pPMD->m_safInfo.glob_trans_count; i++) {
            if (pme->u.m_pPMD->m_safInfo.glob_trans_count > 0) {
               
               uint8  transpIdx = *pTransp;

               //We just pick the first transparent color
               if(transpIdx < numColors) {

                   // Since the NativeColor of an RGB value is just it's palette index
                   // use the palette index.
                   IBITMAP_SetTransparencyColor (pBitmap, transpIdx);

                   // Set the transparency flag
                   pme->u.m_pPMD->m_transparency = TRUE;
               }
            }
         }
         else
            pme->u.m_pPMD->m_pISafBitmap->cntRGB = 0;
      }
      else {
         pme->u.m_pPMD->m_SAFBitDepth = pme->u.m_pPMD->m_safInfo.bitdepth;
         switch(pme->u.m_pPMD->m_safInfo.bitdepth) {
         case 8:
         {
            uint32 i, red, green, blue;
            pme->u.m_pPMD->m_pISafBitmap->pRGB = MALLOC(256 * 4);
            if(pme->u.m_pPMD->m_pISafBitmap->pRGB)
            {
               pme->u.m_pPMD->m_pISafBitmap->cntRGB = 256;
               for (i = 0; i < 256; ++i) {
                  red = (i >> 5) & 0x07;
                  green = (i >> 2) & 0x07;
                  blue = i & 0x03;
                  pme->u.m_pPMD->m_pISafBitmap->pRGB[i] = (((blue * 255 / 3))
                     | ((green * 255 / 7) << 8)
                     | (red * 255 / 7) << 16);
               }
            }
            break;
         }
         case 12:
            pme->u.m_pPMD->m_pISafBitmap->nColorScheme = IDIB_COLORSCHEME_444;
            break;
         case 16:
            pme->u.m_pPMD->m_pISafBitmap->nColorScheme = IDIB_COLORSCHEME_565;
            break;
         case 24:
            pme->u.m_pPMD->m_pISafBitmap->nColorScheme = IDIB_COLORSCHEME_888;
            break;
         }
      }
   }


   return;
}

/*==================================================================

==================================================================*/
static void SAFLoadFrameToDIB(void * pUser) {
   CMediaBg *   pme = (CMediaBg *)pUser;
   int nImgBytes;
   IDIB * pIDIB;

   if (!pme || !pme->u.m_pPMD)
      return;

   pIDIB = pme->u.m_pPMD->m_pISafBitmap;
   if(!pIDIB || !pme->u.m_pPMD->m_safData.buffer)
      return;

   nImgBytes = pIDIB->cx * pIDIB->cy * (pme->u.m_pPMD->m_SAFBitDepth/8);
   MEMCPY(pIDIB->pBmp, pme->u.m_pPMD->m_safData.buffer, nImgBytes);

   //if(psd->pic_data.pic_data == CMX_AF_PIC_DATA_DONE)
   pme->u.m_pPMD->m_fSafDraw |= OEMMM_DISP_CLEAR;
   pme->u.m_pPMD->m_pIDispBitmap = pme->u.m_pPMD->m_pISafBitmap;

   MEMCPY(&pme->u.m_pPMD->m_dispRect, &pme->u.m_pPMD->m_rectImage,
          sizeof(AEERect));
   pme->u.m_pPMD->m_dispRect.dx = pme->u.m_pPMD->m_pISafBitmap->cx;
   pme->u.m_pPMD->m_dispRect.dy = pme->u.m_pPMD->m_pISafBitmap->cy;

   SetSAFOffsets(pme);

   //Update screen
   AEE_ResumeCallback(&pme->u.m_pPMD->m_cbPMDImage, pme->m_pac);

   return;
}
/*==================================================================

==================================================================*/
static void  SetImageOffsets(CMediaBg * pme) {

   AEERect *pRect;

   if (!pme->u.m_pPMD)
      return;

   pRect = &(pme->u.m_pPMD->m_rectImage);

   switch(pme->u.m_pPMD->m_fJust & 0x0f) {
      case OEMMM_JUST_LEFT:
         pme->u.m_pPMD->m_dispRect.x = pRect->x;
         break;
      case OEMMM_JUST_RIGHT:
         pme->u.m_pPMD->m_dispRect.x = pRect->x + pRect->dx - pme->u.m_pPMD->m_dispRect.dx;
         break;
      case OEMMM_JUST_XCENTR:
         pme->u.m_pPMD->m_dispRect.x = (pRect->x + pRect->dx - pme->u.m_pPMD->m_dispRect.dx)/2; 
         break;
      case OEMMM_JUST_XPCENT:
         pme->u.m_pPMD->m_dispRect.x = pRect->x + ((pRect->dx - pme->u.m_pPMD->m_dispRect.dx) * pme->u.m_pPMD->m_xJustPcent_Pixel)/100;
         break;
      case OEMMM_JUST_XPIXEL:
         pme->u.m_pPMD->m_dispRect.x = pRect->x + pme->u.m_pPMD->m_xJustPcent_Pixel;
         break;
      default:
         pme->u.m_pPMD->m_dispRect.x = pRect->x;
   }

   switch(pme->u.m_pPMD->m_fJust & 0xf0) {
      case OEMMM_JUST_TOP:
         pme->u.m_pPMD->m_dispRect.y = pRect->y;
         break;
      case OEMMM_JUST_BOT:
         pme->u.m_pPMD->m_dispRect.y = pRect->y + pRect->dy - pme->u.m_pPMD->m_dispRect.dy;
         break;
      case OEMMM_JUST_YCENTR:
         pme->u.m_pPMD->m_dispRect.y = pRect->y + (pRect->dy - pme->u.m_pPMD->m_dispRect.dy)/2; 
         break;
      case OEMMM_JUST_YPCENT:
         pme->u.m_pPMD->m_dispRect.y = pRect->y + ((pRect->dy - pme->u.m_pPMD->m_dispRect.dy) * pme->u.m_pPMD->m_yJustPcent_Pixel)/100;
         break;
      case OEMMM_JUST_YPIXEL:
         pme->u.m_pPMD->m_dispRect.y = pRect->y + pme->u.m_pPMD->m_yJustPcent_Pixel;
         break;
      default:
         pme->u.m_pPMD->m_dispRect.y = pRect->y;
   }

   if (pme->u.m_pPMD->m_dispRect.x < pRect->x)
   {
      pme->u.m_pPMD->m_offsetRect.x = (pRect->x - pme->u.m_pPMD->m_dispRect.x);
      pme->u.m_pPMD->m_dispRect.x = pRect->x;
   }
   else
      pme->u.m_pPMD->m_offsetRect.x = 0;

   if (pme->u.m_pPMD->m_dispRect.y < pRect->y)
   {
      pme->u.m_pPMD->m_offsetRect.y = (pRect->y - pme->u.m_pPMD->m_dispRect.y);
      pme->u.m_pPMD->m_dispRect.y = pRect->y;
   }
   else
      pme->u.m_pPMD->m_offsetRect.y = 0;

   MEMCPY (&pme->u.m_pPMD->m_picDispRect, &pme->u.m_pPMD->m_dispRect, 
           sizeof (AEERect));

   MEMCPY (&pme->u.m_pPMD->m_picOffsetRect, &pme->u.m_pPMD->m_offsetRect, 
           sizeof (AEERect));
   pme->u.m_pPMD->m_displayedPic = TRUE;

}

/*==================================================================

==================================================================*/
static void  SetSAFOffsets(CMediaBg * pme) {

   AEERect *pRect;

   if (!pme->u.m_pPMD)
      return;

   pRect = &(pme->u.m_pPMD->m_rectImage);

   //Set display horizontal position
   switch(pme->u.m_pPMD->m_safData.frame_pos.xpos_type) {
      case CMX_AF_POS_LT:
         pme->u.m_pPMD->m_dispRect.x = pRect->x;
         break;
      case CMX_AF_POS_CENTER:
         pme->u.m_pPMD->m_dispRect.x = (pRect->x + pRect->dx - pme->u.m_pPMD->m_dispRect.dx)/2; 
         break;
      case CMX_AF_POS_RB:
         pme->u.m_pPMD->m_dispRect.x = pRect->x + pRect->dx - pme->u.m_pPMD->m_dispRect.dx;
         break;
      case CMX_AF_POS_PIXEL:
         pme->u.m_pPMD->m_dispRect.x = pme->u.m_pPMD->m_safData.frame_pos.xpos_num;
         break;
      default:
         pme->u.m_pPMD->m_dispRect.x = (pRect->x + pRect->dx - pme->u.m_pPMD->m_dispRect.dx)/2; 
   }
   
   //Set display vertical position
   switch(pme->u.m_pPMD->m_safData.frame_pos.ypos_type) {
      case CMX_AF_POS_LT:
         pme->u.m_pPMD->m_dispRect.y = pRect->y;
         break;
      case CMX_AF_POS_CENTER:
         pme->u.m_pPMD->m_dispRect.y = pRect->y + (pRect->dy - pme->u.m_pPMD->m_dispRect.dy)/2;
         break;
      case CMX_AF_POS_RB:
         pme->u.m_pPMD->m_dispRect.y = pRect->y + pRect->dy - pme->u.m_pPMD->m_dispRect.dy;
         break;
      case CMX_AF_POS_PIXEL:
         pme->u.m_pPMD->m_dispRect.y = pme->u.m_pPMD->m_safData.frame_pos.ypos_num;
         break;
      default:
         pme->u.m_pPMD->m_dispRect.y = pRect->y + (pRect->dy - pme->u.m_pPMD->m_dispRect.dy)/2;
         break;
   }
 
        

   if (pme->u.m_pPMD->m_dispRect.x < pRect->x)
   {
      pme->u.m_pPMD->m_offsetRect.x = (pRect->x - pme->u.m_pPMD->m_dispRect.x);
      pme->u.m_pPMD->m_dispRect.x = pRect->x;
   }
   else
      pme->u.m_pPMD->m_offsetRect.x = 0;

   if (pme->u.m_pPMD->m_dispRect.y < pRect->y)
   {
      pme->u.m_pPMD->m_offsetRect.y = (pRect->y - pme->u.m_pPMD->m_dispRect.y);
      pme->u.m_pPMD->m_dispRect.y = pRect->y;
   }
   else
      pme->u.m_pPMD->m_offsetRect.y = 0;

   MEMCPY (&pme->u.m_pPMD->m_safDispRect, &pme->u.m_pPMD->m_dispRect, 
           sizeof (AEERect));

   MEMCPY (&pme->u.m_pPMD->m_safOffsetRect, &pme->u.m_pPMD->m_offsetRect, 
           sizeof (AEERect));
   pme->u.m_pPMD->m_displayedSaf = TRUE;

}

/*==================================================================
Called in user context allocate a JPEG buffer
==================================================================*/
static void AllocJPEGBuf(void * pUser)
{
   CMediaBg *   pme = (CMediaBg *)pUser;

   if (!pme || !pme->u.m_pPMD)
      return;

   if (pme->u.m_pPMD->m_pJPEGBuf)
      FREE(pme->u.m_pPMD->m_pJPEGBuf);
      
   pme->u.m_pPMD->m_pJPEGBuf = (byte *)MALLOC(pme->u.m_pPMD->m_pJPEGBufSize);

   if(pme->u.m_pPMD->m_cbJPEGBufAllocNotify != NULL)
   {
      pme->u.m_pPMD->m_cbJPEGBufAllocNotify(pme->u.m_pPMD->m_pJPEGBuf,
                                            pme->u.m_pPMD->m_pJPEGClientData);
   }
}

/*==================================================================
Called in user context to free a JPEG buffer allocated in user context
==================================================================*/
static void FreeJPEGBuf(void * pUser)
{
   CMediaBg *   pme = (CMediaBg *)pUser;

   if (!pme || !pme->u.m_pPMD)
      return;

   FREEIF(pme->u.m_pPMD->m_pJPEGBuf);
   pme->u.m_pPMD->m_pJPEGBuf = NULL;
   pme->u.m_pPMD->m_pJPEGBufSize = 0;
   pme->u.m_pPMD->m_cbJPEGBufAllocNotify = NULL;
   pme->u.m_pPMD->m_pJPEGClientData = NULL;
}

/*==================================================================
Called in user context allocate a buffer.
Once allocated, these buffers will probably exist as long as the
object exists, so there's no need for complicated management.
The size of the list is expected to stay very small (~3).
==================================================================*/
static void AllocClientBuf(void * pUser)
{
   CMediaBg *   pme = (CMediaBg *)pUser;
   int i;
   uint8 ** pp;

   if (!pme || !pme->u.m_pPMD)
      return;

   // Check if a buffer in the list is unallocated
   for (i=0; i<pme->u.m_pPMD->m_numClientBufs; ++i)
   {
      if (!pme->u.m_pPMD->m_ppClientBufs[i])
      break;
   }
   
   // If no buffers in the list are free, expand the list
   if (i == pme->u.m_pPMD->m_numClientBufs)
   {
      pp = REALLOC(pme->u.m_pPMD->m_ppClientBufs, sizeof(uint8*) * (pme->u.m_pPMD->m_numClientBufs + OEMMM_CLIENT_BUF_INIT_COUNT));
      if (pp)
      {
         pme->u.m_pPMD->m_ppClientBufs = pp;
         pme->u.m_pPMD->m_numClientBufs += OEMMM_CLIENT_BUF_INIT_COUNT;
      }    
      else
      {
         pme->u.m_pPMD->m_lastBufAllocated = -1;
         return;
      }
   }
   
   // Allocate the buffer in the first free slot
   pme->u.m_pPMD->m_ppClientBufs[i] = MALLOC(pme->u.m_pPMD->m_ClientBufSize);
   if (pme->u.m_pPMD->m_ppClientBufs[i])
   {
      pme->u.m_pPMD->m_lastBufAllocated = i;
   }
   else
   {
      pme->u.m_pPMD->m_lastBufAllocated = -1;
   }   

   // Pass the buffer back to cmx
   if(pme->u.m_pPMD->m_cbClientBufAllocNotify != NULL)
   {
      pme->u.m_pPMD->m_cbClientBufAllocNotify(pme->u.m_pPMD->m_ppClientBufs[i],
                                              pme->u.m_pPMD->m_pBufClientData);
   }
}


/*==================================================================
Called in user context to free a buffer allocated in user context
==================================================================*/
static void FreeClientBuf(void * pUser)
{
   CMediaBg *   pme = (CMediaBg *)pUser;
   int i;

   if (!pme || !pme->u.m_pPMD || !pme->u.m_pPMD->m_ppClientBufs || !pme->u.m_pPMD->m_ClientBufSent)
      return;

   //Find the buffer in the list and free it
   for (i=0; i<pme->u.m_pPMD->m_numClientBufs; ++i)
   {
      if (pme->u.m_pPMD->m_ppClientBufs[i] == pme->u.m_pPMD->m_ClientBufSent)
      {
         FREE(pme->u.m_pPMD->m_ClientBufSent);
         pme->u.m_pPMD->m_ClientBufSent = pme->u.m_pPMD->m_ppClientBufs[i] = NULL;
         break;
      }
   }
}

/*==================================================================
Checks if the address passed in matches one of the client-allocated
buffers.  Returns the index of the client-allocated buffer, or
-1 if no match.
==================================================================*/
/*
static int GetClientBufIndex(CMediaBg * pme, uint8 ** ppClientBuf)
{
   int i;

   if (!pme || !pme->u.m_pPMD || !pme->u.m_pPMD->m_ppClientBufs)
      return -1;

   // Check if a buffer in the list matches the one passed
   for (i=0; i<pme->u.m_pPMD->m_numClientBufs; ++i)
   {
      if (pme->u.m_pPMD->m_ppClientBufs[i] == *ppClientBuf)
      break;
   }
   
   // If no buffers in the list matched, return -1
   if (i == pme->u.m_pPMD->m_numClientBufs)
      return -1;
   else
      return i;
}
*/   


#endif // FEATURE_WEBAUDIO

#endif // defined(FEATURE_AUDIO_FORMAT)

#if defined(FEATURE_MIDI_OUT)

/*===========================================================================

                      CMX: MIDIOutMsg and MIDIOutQCP

===========================================================================*/
static void CMediaMMLayer_MIDIOutNotify(CMediaCMX * pme, boolean bSuccess);

/*==================================================================

   CMX MIDI Device Functions

==================================================================*/
static int CMIDIOut_AddRef(void)
{
   return ++gsMIDIOut.m_nRef;
}

static int CMIDIOut_Release(void)
{
   if (!--gsMIDIOut.m_nRef)
   {
      OEMMEDIA_SetParm(NULL, OEMCMX_ID_MIDI_DEVICE, (int32)FALSE, 0, 0);
      MEMSET(&gsMIDIOut, (int)0, sizeof(CMIDIOut));
   }

   return gsMIDIOut.m_nRef;
}

static int CMIDIOut_Open(byte nADECType, PFNMIDIOUTNOTIFY pfn, CMediaCMX * pme)
{
   int   nRet = SUCCESS;

   if (!pfn)
      return EBADPARM;

   if (!CMIDIOut_IsOpen())
   {
      if (!gsMIDIOut.m_bOpenPending)
      {
         gsMIDIOut.m_nADECType = nADECType;
         gsMIDIOut.m_bOpenPending = TRUE;
         gsMIDIOut.m_pfnNotify = pfn;
         gsMIDIOut.m_pMM = pme;
         CALLBACK_Init(&gsMIDIOut.m_cb, CMIDIOut_OpenNotify, NULL);
#if defined(FEATURE_BREW_CMX_V3)
         {
            CMXInfo  ci;
            ci.nType = OEMCMX_TYPE_MIDI_OUT_QCP;
            ci.ph = NULL;
            ci.dwCB = (uint32)CMIDIOut_MIDIDeviceCB;
            ci.dwUserData = (uint32)pme->m_hObject;
            OEMMEDIA_SetParm(NULL, OEMCMX_ID_MIDI_DEVICE, (int32)TRUE, (int32)nADECType, (int32)&ci);
         }
#else // CMX 2.x
         cmx_midi_out_open(CMIDIOut_MIDIDeviceCB, (void *)pme->m_hObject);
#endif // defined(FEATURE_BREW_CMX_V3)
      }
      else
         nRet = MM_EMIDIOUTNOTOPEN;
   }
   else
   {
      CMIDIOut_AddRef();
      BRIDLE_ASSERT_SAFE_PFN(pfn);
      pfn(pme, TRUE);
   }

   return nRet;
}

void CMIDIOut_MIDIDeviceCB(cmx_status_type status, const void * pClientData)
{
   CMediaCMX * pMedia ;
   boolean     bResume = FALSE;

   OEMMM_ENTER_CRITICAL_SECTION()

   pMedia = (CMediaCMX *)AEEObjectMgr_GetObject((AEEObjectHandle)pClientData);

   BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, &gsMIDIOut, sizeof(CMIDIOut));

   if (!pMedia)
   {
      gsMIDIOut.m_bOpenPending = FALSE;
      goto OEMMM_CBExit;
   }

   switch (status)
   {
      case CMX_ENABLED: // open succeeded.
      case CMX_FAILURE: // for open() failure or prev midi cmd failure
         if (!CMIDIOut_IsOpen())
         {
            gsMIDIOut.m_cb.pNotifyData = (void *)status;
            bResume = TRUE;
         }
         break;

      case CMX_DISABLED: // when close() happens
         break;

      case CMX_SUCCESS: // for open() queueing, prev midi cmd success
         break;

      case CMX_DONE: // never sent??
         break;
   }

OEMMM_CBExit:

   OEMMM_LEAVE_CRITICAL_SECTION()

   if (bResume)
      AEE_ResumeCallback(&gsMIDIOut.m_cb,0); // Sys resume.
}

static void CMIDIOut_OpenNotify(void * pUser)
{
   CMediaCMX * pme = (CMediaCMX *)AEEObjectMgr_GetObject(gsMIDIOut.m_pMM->m_hObject);
   boolean     bOpen = ((cmx_status_type)(uint32)pUser) == CMX_ENABLED;

   gsMIDIOut.m_bOpenPending = FALSE;

   if (!pme)
      return;

   if (bOpen)
      CMIDIOut_AddRef();

   BRIDLE_ASSERT_SAFE_PFN(gsMIDIOut.m_pfnNotify);
   gsMIDIOut.m_pfnNotify(gsMIDIOut.m_pMM, bOpen);
}

static void CMediaMMLayer_MIDIOutNotify(CMediaCMX * pme, boolean bSuccess)
{
   pme->m_bMIDIOut = bSuccess;

   if (pme->m_clsSelf == AEECLSID_MEDIAMIDIOUTMSG)
   {
      if (bSuccess)
         CMediaMMLayer_MIDIOutMsg((CMediaMIDIOutMsg *)pme);
   }
   else if (pme->m_clsSelf == AEECLSID_MEDIAMIDIOUTQCP || pme->m_clsSelf == AEECLSID_MEDIAADPCM)
   {
#if defined(FEATURE_MIDI_OUT_QCP)
      if (bSuccess)
         CMediaMMLayer_MIDIOutQCPPlay((CMediaMIDIOutQCP *)pme);
      else
         CMediaMMLayer_MIDIOutQCPStatusCB(CMX_FAILURE, (void *)pme->m_hObject);
#endif // defined(FEATURE_MIDI_OUT_QCP)
   }
}

static int CMediaMMLayer_MIDIOutMsg(CMediaMIDIOutMsg * pme)
{
   // If MIDIOut device is not open, then issue an open command and return.
   // The callback of the MIDI open will increment MIDI device ref and 
   // calls the registered callback which calls this function to play the media
   if (!pme->m_bMIDIOut)
      return CMIDIOut_Open((byte)OEMMM_ADEC_MODE_DEFAULT, CMediaMMLayer_MIDIOutNotify, (CMediaCMX *)pme);

   {
      CMediaMIDIOutMsg *   p = (CMediaMIDIOutMsg *)pme;
      cmx_handle_type      h;
      CMXInfo              ci;

      h.source_type = h.mem.source_type = CMX_SOURCE_MEM;
      h.mem.buf = p->m_pMsg;
      h.mem.len = p->m_dwSize;

      ci.nType = OEMCMX_TYPE_MIDI_OUT_MSG;
      ci.ph = &h;
      ci.dwCB = (uint32)CMediaMMLayer_MIDIOutMsgCB;
      ci.dwUserData = (uint32)pme->m_hObject;
      OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_TYPE, (int32)&ci, 0, 0);
      OEMMEDIA_Start(pme->m_hMediaCMX);
   }

   return SUCCESS;
}

void CMediaMMLayer_MIDIOutMsgCB(cmx_status_type status, const void * pClientData)
{
   CMediaMIDIOutMsg *   pme;
   AEEMediaCallback *   pcb1 = NULL;
   AEEMediaCallback *   pcb2 = NULL;
   boolean              bResume1 = FALSE;
   boolean              bResume2 = FALSE;

   OEMMM_ENTER_CRITICAL_SECTION()

   pme = (CMediaMIDIOutMsg *)AEEObjectMgr_GetObject((AEEObjectHandle)pClientData);
   if (!pme)
      goto OEMMM_CBExit;

   BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme, sizeof(CMediaMIDIOutMsg));

   // In this callback, send MM_STATUS_START followed by 
   // MM_STATUS_DONE/MM_STATUS_ABORT based on CMX status.
   
   pcb1 = CMediaMMLayer_AllocCallback(pme->m_cbPlay, OEMMM_MAX_CB_PLAY, MM_CMD_PLAY, 0);
   if (!pcb1)
      goto OEMMM_CBExit;

   pcb1->cmdNotify.nStatus = MM_STATUS_START;
   bResume1 = TRUE;

   pcb2 = CMediaMMLayer_AllocCallback(pme->m_cbPlay, OEMMM_MAX_CB_PLAY, MM_CMD_PLAY, 0);
   if (!pcb2)
   {
      pcb1->cmdNotify.nStatus = MM_STATUS_ABORT;
      goto OEMMM_CBExit;
   }

   switch (status)
   {
      case CMX_SUCCESS:
         pcb2->cmdNotify.nStatus = MM_STATUS_DONE;
         bResume2 = TRUE;
         break;

      case CMX_FAILURE:
         pcb2->cmdNotify.nStatus = MM_STATUS_ABORT;
         bResume2 = TRUE;
         break;

      default:
         CMediaMMLayer_FreeCallback(pcb2);
         break;
   }

OEMMM_CBExit:

   OEMMM_LEAVE_CRITICAL_SECTION()

   if (bResume1)
      AEE_ResumeCallback(&pcb1->cb, pme->m_pac);
   if(bResume2)
      AEE_ResumeCallback(&pcb2->cb, pme->m_pac);
}

#if defined(FEATURE_MIDI_OUT_QCP)
/*==================================================================

   CMX MIDIOutQCP Functions

==================================================================*/
static int CMediaMMLayer_MIDIOutQCPPlay(CMediaMIDIOutQCP * pme)
{
   cmx_handle_type      h;
   int                  nRet = SUCCESS;
   CMXInfo              ci;

   // If MIDIOut device is not open, then issue an open command and return.
   // The callback of the MIDI open will increment MIDI device ref and 
   // calls the registered callback which calls this function to play the media
   // Make sure that QCP is played via ADEC_QCP and ADPCM is played via ADEC_ADPCM...
   if (!pme->m_bMIDIOut)
   {
      int   nADECType = -1;
      
      if (AEECLSID_MEDIAMIDIOUTQCP == pme->m_clsSelf)
      {
         if (CMIDIOut_IsQCP())
            nADECType = OEMMM_ADEC_MODE_QCP;
      }
      else if (AEECLSID_MEDIAADPCM == pme->m_clsSelf)
      {
         if (CMIDIOut_IsADPCM())
            nADECType = OEMMM_ADEC_MODE_ADPCM;
      }

      if (nADECType > -1)
         return CMIDIOut_Open((byte)nADECType, CMediaMMLayer_MIDIOutNotify, (CMediaCMX *)pme);
      else
         return EFAILED;
   }

   nRet = CMediaMMLayer_SetSource((CMediaCMX *)pme, &h);
   if (nRet != SUCCESS)
      return nRet;

   ci.nType = OEMCMX_TYPE_MIDI_OUT_QCP;
   ci.ph = &h;
   ci.dwCB = (uint32)CMediaMMLayer_MIDIOutQCPPlayCB;
   ci.dwUserData = (uint32)pme->m_hObject;
   OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_TYPE, (int32)&ci, 0, 0);
   OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_VOLUME, (int32)pme->m_wVolume, 0, 0);
   OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_PAN, (int32)(pme->m_wPan - 64), 0, 0);
   OEMMEDIA_Start(pme->m_hMediaCMX);

   // MM_STATUS_START is sent in the CMX_SUCCESS of CMediaMMLayer_MIDIOutQCPPlayCB()

   return SUCCESS;
}

static int CMediaMMLayer_MIDIOutQCPStop(CMediaMIDIOutQCP * pme, uint32 dwChannel)
{
   AEEMediaCallback *   pcb = &pme->m_cbCommand;

   // Callback must always be available for stop command.
   CMediaMMLayer_FreeCallback(pcb);
   CMediaMMLayer_AllocCallback(&pme->m_cbCommand, 1, MM_CMD_PLAY, OEMMM_CTLCMD_STOP);

   OEMMEDIA_Stop(pme->m_hMediaCMX);

   return SUCCESS;
}

static int CMediaMMLayer_MIDIOutQCPPause(CMediaMIDIOutQCP * pme)
{
   AEEMediaCallback *   pcb;

   pcb = CMediaMMLayer_AllocCallback(&pme->m_cbCommand, 1, MM_CMD_PLAY, OEMMM_CTLCMD_PAUSE);
   if (!pcb)
      return EITEMBUSY;

   OEMMEDIA_Pause(pme->m_hMediaCMX);

   return SUCCESS;
}

static int CMediaMMLayer_MIDIOutQCPResume(CMediaMIDIOutQCP * pme)
{
   AEEMediaCallback *   pcb;

   pcb = CMediaMMLayer_AllocCallback(&pme->m_cbCommand, 1, MM_CMD_PLAY, OEMMM_CTLCMD_RESUME);
   if (!pcb)
      return EITEMBUSY;

   OEMMEDIA_Resume(pme->m_hMediaCMX);

   return SUCCESS;
}

static int CMediaMMLayer_MIDIOutQCPSetVolume(CMediaMIDIOutQCP * pme, uint32 dwChannel, uint16 wVolume)
{
   AEEMediaCallback *   pcb;

   if (wVolume > AEE_MAX_VOLUME)
      wVolume = AEE_MAX_VOLUME;

   pcb = CMediaMMLayer_AllocCallback(&pme->m_cbCommand, 1, MM_CMD_SETMEDIAPARM, MM_PARM_VOLUME);
   if (!pcb)
      return EITEMBUSY;

   // Scale the volume here...
   pme->m_wVolume = (uint16)((CMX_MIDI_OUT_QCP_DEFAULT_VOLUME / AEE_MAX_VOLUME) * wVolume);

   OEMMEDIA_SetParm(pme->m_hMediaCMX, OEMCMX_ID_VOLUME, (int32)pme->m_wVolume, 0, 0);

   return SUCCESS;
}

static int CMediaMMLayer_MIDIOutQCPSetPan(CMediaMIDIOutQCP * pme, uint32 dwChannel, uint16 wPan)
{
   AEEMediaCallback *   pcb;
   sint15               nScaledPan;

   if (wPan > MM_MAX_PAN)
      return EBADPARM;

   pcb = CMediaMMLayer_AllocCallback(&pme->m_cbCommand, 1, MM_CMD_SETMEDIAPARM, MM_PARM_PAN);
   if (!pcb)
      return EITEMBUSY;

   pme->m_wPan = wPan;
   // Scale the pan here...
   nScaledPan = (sint15)(wPan - 64);

   OEMMEDIA_SetParm(pme->m_hMediaCMX, OEMCMX_ID_PAN, (int32)nScaledPan, 0, 0);

   return SUCCESS;
}

void CMediaMMLayer_MIDIOutQCPStatusCB(cmx_status_type status, const void * pClientData)
{
   AEEMediaCallback *   pcb = NULL;
   CMediaMIDIOutQCP *   pme;
   boolean              bResume = FALSE;

   OEMMM_ENTER_CRITICAL_SECTION()

   pme = (CMediaMIDIOutQCP *)AEEObjectMgr_GetObject((AEEObjectHandle)pClientData);
   if (!pme)
      goto OEMMM_CBExit;

   BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme, sizeof(CMediaMIDIOutQCP));

   pcb = &pme->m_cbCommand;
   if (!pcb->bInUse)
      goto OEMMM_CBExit;

   switch (status)
   {
      case CMX_SUCCESS:
         if (CMediaMMLayer_ISPAUSE(pcb))
            pcb->cmdNotify.nStatus = MM_STATUS_PAUSE;
         else if (CMediaMMLayer_ISRESUME(pcb))
            pcb->cmdNotify.nStatus = MM_STATUS_RESUME;
         else if (!CMediaMMLayer_ISSTOP(pcb))  // do not send stop done as play done is sent.
            pcb->cmdNotify.nStatus = MM_STATUS_DONE;

         pcb->cmdNotify.nSubCmd = 0;
         break;

      case CMX_FAILURE:
         if (CMediaMMLayer_ISPAUSE(pcb))
            pcb->cmdNotify.nStatus = MM_STATUS_PAUSE_FAIL;
         else if (CMediaMMLayer_ISRESUME(pcb))
            pcb->cmdNotify.nStatus = MM_STATUS_RESUME_FAIL;
         else if (!CMediaMMLayer_ISSTOP(pcb))  // do not send stop done as play done is sent.
            pcb->cmdNotify.nStatus = MM_STATUS_ABORT;

         pcb->cmdNotify.nSubCmd = 0;
         break;
   }

   bResume = TRUE;

OEMMM_CBExit:

   OEMMM_LEAVE_CRITICAL_SECTION()

   if (bResume)
      AEE_ResumeCallback(&pcb->cb, pme->m_pac);
}

void CMediaMMLayer_MIDIOutQCPPlayCB(cmx_status_type status, const void * midi_out_qcp_id, const void * pClientData)
{
   CMediaMIDIOutQCP *   pme;
   AEEMediaCallback *   pcb = NULL;
   boolean              bResume = FALSE;

   OEMMM_ENTER_CRITICAL_SECTION()

   pme = (CMediaMIDIOutQCP *)AEEObjectMgr_GetObject((AEEObjectHandle)pClientData);
   if (!pme)
      goto OEMMM_CBExit;

   BRIDLE_CHECK_WRITEACCESS(BRIDLE_FROM_UNPRIV, pme, sizeof(CMediaMIDIOutQCP));

   pcb = CMediaMMLayer_AllocCallback(pme->m_cbPlay, OEMMM_MAX_CB_PLAY, MM_CMD_PLAY, 0);
   if (!pcb)
      goto OEMMM_CBExit;

   switch (status)
   {
      case CMX_SUCCESS:
      {
         CMediaMIDIOutQCP *   p = (CMediaMIDIOutQCP *)pme;
         p->m_dwChannel = (uint32)midi_out_qcp_id;
         OEMMEDIA_SetParmLocal(pme->m_hMediaCMX, OEMCMX_ID_CHANNEL, (int32)p->m_dwChannel, 0, 0);
         pcb->cmdNotify.nStatus = MM_STATUS_START;
         break;
      }

      case CMX_FAILURE:
         pcb->cmdNotify.nStatus = MM_STATUS_ABORT;
         break;

      //rr required??
      case CMX_DONE:
         pcb->cmdNotify.nStatus = MM_STATUS_DONE;
         break;
   }

   bResume = TRUE;

OEMMM_CBExit:

   OEMMM_LEAVE_CRITICAL_SECTION()

   if (bResume)
      AEE_ResumeCallback(&pcb->cb, pme->m_pac);
}

#endif // defined(FEATURE_MIDI_OUT_QCP)
#endif // defined(FEATURE_MIDI_OUT)

/*===========================================================================

  If FEATURE_AUDIO_FORMAT is not defined, then 
  define the following stubs.

===========================================================================*/
#if !defined(FEATURE_AUDIO_FORMAT)

static int CMediaMMLayer_Play(CMediaBg * pme)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_Stop(CMediaBg * pme)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_Seek(CMediaBg * pme, AEEMediaSeek eSeek, int32 lTimeMS)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_Pause(CMediaBg * pme)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_Resume(CMediaBg * pme)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_GetTotalTime(CMediaBg * pme)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_SetTempo(CMediaBg * pme, uint32 dwTempo)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_SetTune(CMediaBg * pme, uint32 dwTune)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_SetPan(CMediaBg * pme, uint32 dwTune)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_QCPRecord(CMediaBg * pme, int nFormat)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_PCMRecord(CMediaBg * pme)
{
   return EUNSUPPORTED;
}

#endif // !defined(FEATURE_AUDIO_FORMAT)

/*===========================================================================

  If FEATURE_MIDI_OUT is not defined, then 
  define the following stubs.

===========================================================================*/
#if !defined(FEATURE_MIDI_OUT)

static int CMediaMMLayer_MIDIOutMsg(CMediaMIDIOutMsg * pme)
{
   return EUNSUPPORTED;
}

#endif // !defined(FEATURE_MIDI_OUT)

#if !defined(FEATURE_MIDI_OUT_QCP)

static int CMediaMMLayer_MIDIOutQCPPlay(CMediaMIDIOutQCP * pme)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_MIDIOutQCPStop(CMediaMIDIOutQCP * pme, uint32 dwChannel)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_MIDIOutQCPPause(CMediaMIDIOutQCP * pme)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_MIDIOutQCPResume(CMediaMIDIOutQCP * pme)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_MIDIOutQCPSetVolume(CMediaMIDIOutQCP * pme, uint32 dwChannel, uint16 wVolume)
{
   return EUNSUPPORTED;
}

static int CMediaMMLayer_MIDIOutQCPSetPan(CMediaMIDIOutQCP * pme, uint32 dwChannel, uint16 wPan)
{
   return EUNSUPPORTED;
}

#endif // !defined(FEATURE_MIDI_OUT_QCP)

/*===============================================================================

      CMediaMain DECLARATIONS
      This is a wrapper IMedia class that routes the calls to currently
      active IMedia object contained in the wrapper class.

===============================================================================*/

OBJECT(CMediaMain)
{
   INHERIT_AEEMedia(IMediaADPCM);

   IMedia *          m_pMedia;
   flg               m_bChannelShare;
};

void IMediaMain_Init(IShell * ps);
int  IMediaMain_New(IShell * ps, AEECLSID cls, void **ppif);

static uint32  CMediaMain_Release(IMedia * po);
static int     CMediaMain_RegisterNotify(IMedia * po, PFNMEDIANOTIFY pfnNotify, void * pUser);
static int     CMediaMain_SetMediaParm(IMedia * po, int nParmID, int32 p1, int32 p2);
static int     CMediaMain_GetMediaParm(IMedia * po, int nParmID, int32 * pP1, int32 * pP2);
static int     CMediaMain_Play(IMedia * po);
static int     CMediaMain_Record(IMedia * po);
static int     CMediaMain_Stop(IMedia * po);
static int     CMediaMain_Seek(IMedia * po, AEEMediaSeek eSeek, int32 lTimeMS);
static int     CMediaMain_Pause(IMedia * po);
static int     CMediaMain_Resume(IMedia * po);
static int     CMediaMain_GetTotalTime(IMedia * po);
static int     CMediaMain_GetState(IMedia * po, boolean * pbStateChanging);

static void    CMediaMain_Delete(CMediaMain * pme);
static int     CMediaMain_CreateMedia(CMediaMain * pme);
static void    CMediaMain_MediaNotify(CMediaMain * pme, AEEMediaCmdNotify * pcn);

static const VTBL(IMediaADPCM) gMediaMainFuncs =
{
   AEEMedia_AddRef,
   CMediaMain_Release,
   AEEMedia_QueryInterface,
   CMediaMain_RegisterNotify,
   CMediaMain_SetMediaParm,
   CMediaMain_GetMediaParm,
   CMediaMain_Play,
   CMediaMain_Record,
   CMediaMain_Stop,
   CMediaMain_Seek,
   CMediaMain_Pause,
   CMediaMain_Resume,
   CMediaMain_GetTotalTime,
   CMediaMain_GetState
};

/*==================================================================

==================================================================*/
void IMediaMain_Init(IShell * ps)
{
   if (CMediaFormat_InModTable(AEECLSID_MEDIAQCP, (void *)IMediaMain_Init, NULL))
   {
      AEEMedia_Init(ps, MT_AUDIO_QCP, AEECLSID_MEDIAQCP);
      AEEMedia_Init(ps, MT_AUDIO_VND_QCELP, AEECLSID_MEDIAQCP);
   }

   if (CMediaFormat_InModTable(AEECLSID_MEDIAADPCM, (void *)IMediaMain_Init, NULL))
      AEEMedia_Init(ps, MT_AUDIO_ADPCM, AEECLSID_MEDIAADPCM);
}

/*==================================================================

==================================================================*/
int IMediaMain_New(IShell * ps, AEECLSID cls, void **ppif)
{
   int            nErr;
   CMediaMain *   pme;

   if (!ppif)
      return EFAILED;
      
   pme = (CMediaMain *)AEE_NewClass((IBaseVtbl *)&gMediaMainFuncs, sizeof(CMediaMain));
   if (!pme)
      return ENOMEMORY;

   if (SUCCESS != AEEMedia_New((IMedia *)pme, ps, cls))
   {
      FREE(pme);
      return ENOMEMORY;
   }

   // Based on class ID, decide the value of bChannelShare...
   if ( (cls == AEECLSID_MEDIAQCP) || (cls == AEECLSID_MEDIAADPCM) ) 
      pme->m_bChannelShare = FALSE;
   else // for ADPCM (and by default, if allowed by device), set to TRUE.
      pme->m_bChannelShare = TRUE;

   nErr = CMediaMain_CreateMedia(pme);
   if (nErr)
   {
      FREE(pme);
      return nErr;
   }

   *ppif = pme;

   return SUCCESS;
}

/*==================================================================

==================================================================*/
static int CMediaMain_CreateMedia(CMediaMain * pme)
{
   int            nErr;
   int            nState;

   if (pme->m_pMedia)
   {
      nState = IMEDIA_GetState(pme->m_pMedia, NULL);
      MM_RELEASEIF(pme->m_pMedia);
   }
   else
      nState = MM_STATE_IDLE;

   if (pme->m_bChannelShare)
   {
      AEECLSID cls = (pme->m_clsSelf == AEECLSID_MEDIAQCP) 
                     ? AEECLSID_MEDIAMIDIOUTQCP : pme->m_clsSelf;
              
      nErr = IMediaMIDIOutQCP_New(pme->m_pIShell, cls, (void **)&pme->m_pMedia);
   }
   else
      nErr = IMediaBg_New(pme->m_pIShell, pme->m_clsSelf, (void **)&pme->m_pMedia);

   if (!nErr && nState != MM_STATE_IDLE)
   {
      nErr = IMEDIA_SetMediaDataEx(pme->m_pMedia, &pme->m_md, 1);
      if (!nErr)
         nErr = IMEDIA_RegisterNotify(pme->m_pMedia, (PFNMEDIANOTIFY)CMediaMain_MediaNotify, pme);
   }

   if (nErr)
   {
      FREE(pme);
      return nErr;
   }

   return SUCCESS;
}

/*==================================================================

==================================================================*/
static uint32 CMediaMain_Release(IMedia * po)
{
   CMediaMain *   pme = (CMediaMain *)po;
   int32          nRef = (int32)AEEMedia_Release(po);

   if (!nRef)
      CMediaMain_Delete(pme);

   return nRef;
}

/*==================================================================

==================================================================*/
static int CMediaMain_RegisterNotify(IMedia * po, PFNMEDIANOTIFY pfnNotify, void * pUser)
{
   CMediaMain *   pme = (CMediaMain *)po;
   int            nErr;

   if (!pme->m_pMedia)
      return EFAILED;

   nErr = AEEMedia_RegisterNotify(po, pfnNotify, pUser);
   if (nErr)
      return nErr;

   return IMEDIA_RegisterNotify(pme->m_pMedia, (PFNMEDIANOTIFY)CMediaMain_MediaNotify, pme);
}

/*==================================================================

==================================================================*/
static int CMediaMain_SetMediaParm(IMedia * po, int nParmID, int32 p1, int32 p2)
{
   CMediaMain *   pme = (CMediaMain *)po;
   int            nErr;

   if (!pme->m_pMedia)
      return EFAILED;

   if (nParmID == MM_PARM_MEDIA_DATA)
   {
      nErr = AEEMedia_SetMediaParm(po, nParmID, p1, p2);
      if (!nErr)
         return IMEDIA_SetMediaParm(pme->m_pMedia, nParmID, p1, p2);
      else
         return nErr;
   }
   else if (nParmID == MM_PARM_CHANNEL_SHARE)
   {
      boolean  bEnable = (boolean)p1;
      if (pme->m_bChannelShare != bEnable)
      {
         pme->m_bChannelShare = bEnable;
         return CMediaMain_CreateMedia(pme);
      }
      return SUCCESS;
   }

   return IMEDIA_SetMediaParm(pme->m_pMedia, nParmID, p1, p2);
}

/*==================================================================

==================================================================*/
static int CMediaMain_GetMediaParm(IMedia * po, int nParmID, int32 * pP1, int32 * pP2)
{
   CMediaMain *   pme = (CMediaMain *)po;

   if (!pme->m_pMedia)
      return EFAILED;

   if (nParmID == MM_PARM_MEDIA_DATA || nParmID == MM_PARM_CLSID)
      return AEEMedia_GetMediaParm(po, nParmID, pP1, pP2);
   else if (nParmID == MM_PARM_CHANNEL_SHARE)
   {
      if (pP1)
         *(boolean *)pP1 = pme->m_bChannelShare;
      return SUCCESS;
   }

   return IMEDIA_GetMediaParm(pme->m_pMedia, nParmID, pP1, pP2);
}

/*==================================================================

==================================================================*/
static int CMediaMain_Play(IMedia * po)
{
   CMediaMain *   pme = (CMediaMain *)po;

   if (!pme->m_pMedia)
      return EFAILED;

   return IMEDIA_Play(pme->m_pMedia);
}

/*==================================================================

==================================================================*/
static int CMediaMain_Record(IMedia * po)
{
   CMediaMain *   pme = (CMediaMain *)po;

   if (!pme->m_pMedia)
      return EFAILED;

   return IMEDIA_Record(pme->m_pMedia);
}

/*==================================================================

==================================================================*/
static int CMediaMain_Stop(IMedia * po)
{
   CMediaMain *   pme = (CMediaMain *)po;

   if (!pme->m_pMedia)
      return EFAILED;

   return IMEDIA_Stop(pme->m_pMedia);
}

/*==================================================================

==================================================================*/
static int CMediaMain_Seek(IMedia * po, AEEMediaSeek eSeek, int32 lTimeMS)
{
   CMediaMain *   pme = (CMediaMain *)po;

   if (!pme->m_pMedia)
      return EFAILED;

   return IMEDIA_Seek(pme->m_pMedia, eSeek, lTimeMS);
}

/*==================================================================

==================================================================*/
static int CMediaMain_Pause(IMedia * po)
{
   CMediaMain *   pme = (CMediaMain *)po;

   if (!pme->m_pMedia)
      return EFAILED;

   return IMEDIA_Pause(pme->m_pMedia);
}

/*==================================================================

==================================================================*/
static int CMediaMain_Resume(IMedia * po)
{
   CMediaMain *   pme = (CMediaMain *)po;

   if (!pme->m_pMedia)
      return EFAILED;

   return IMEDIA_Resume(pme->m_pMedia);
}

/*==================================================================

==================================================================*/
static int CMediaMain_GetTotalTime(IMedia * po)
{
   CMediaMain *   pme = (CMediaMain *)po;

   if (!pme->m_pMedia)
      return EFAILED;

   return IMEDIA_GetTotalTime(pme->m_pMedia);
}

/*==================================================================

==================================================================*/
static int CMediaMain_GetState(IMedia * po, boolean * pbStateChanging)
{
   CMediaMain *   pme = (CMediaMain *)po;

   if (pme->m_pMedia)
      pme->m_nState = IMEDIA_GetState(pme->m_pMedia, &pme->m_bStateChanging);

   if (pbStateChanging)
      *pbStateChanging = pme->m_bStateChanging;

   return pme->m_nState;
}

/*==================================================================

==================================================================*/
static void CMediaMain_Delete(CMediaMain * pme)
{
   MM_RELEASEIF(pme->m_pMedia);

   // Call base class destructor
   AEEMedia_Delete((IMedia *)pme);

   FREE(pme);
}

/*==================================================================

==================================================================*/
static void CMediaMain_MediaNotify(CMediaMain * pme, AEEMediaCmdNotify * pcn)
{
   // Update the state...
   CMediaMain_GetState((IMedia *)pme, NULL);

   // Update the notify info...
   if (pcn && pme->m_pfnNotify)
   {
      pcn->pIMedia = (IMedia *)pme;
      pcn->clsMedia = pme->m_clsSelf;
      BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnNotify);
      pme->m_pfnNotify(pme->m_pUser, pcn);
   }
}

#endif // defined(FEATURE_BREW_MULTIMEDIA)
