windows-nt/Source/XPSP1/NT/multimedia/directx/dplay/dvoice/dxvoice/dvcleng.h
2020-09-26 16:20:57 +08:00

494 lines
21 KiB
C++

/*==========================================================================
*
* Copyright (C) 1999 Microsoft Corporation. All Rights Reserved.
*
* File: dvclientengine.h
* Content: Definition of class for DirectXVoice Client
*
* History:
* Date By Reason
* ==== == ======
* 07/06/99 rodtoll Created it
* 07/21/99 rodtoll Added flags field to entity data
* 07/26/99 rodtoll Updated to support IDirectXVoiceNotify interface
* 08/25/99 rodtoll General Cleanup/Modifications to support new
* compression sub-system.
* 08/30/99 rodtoll Added additional members to handle connect/disconnect timeout
* 08/31/99 rodtoll Added new handle for new disconnect procedure
* 09/03/99 rodtoll Re-work of playback core to support mixing to multiple buffers
* for 3d support.
* Re-worked playback to use position instead of notifications
* allows for simpler handling of high CPU and 3d support
* Implemented CreateUserBuffer/DeleteUserBuffer
* 09/14/99 rodtoll Added members to support new notify masks
* rodtoll Added CheckShouldSendMessage
* rodtoll Added SendPlayerLevels
* rodtoll Updated initialize for new parameters
* rodtoll Added SetNotifyMask function
* 09/18/99 rodtoll Added HandleThreadError to be called when an internal thread dies.
* 09/27/99 rodtoll Added playback volume control
* rodtoll Added reduction of playback volume when transmitting
* 09/28/99 rodtoll Double notifications of local client when host migrates fixed.
* rodtoll Added queue for notifications, notifications are added to the queue and
* then signalled by the notify thread. (Prevents problems caused by notify
* handlers taking a long time to return).
* 09/29/99 pnewson Major AGC overhaul
* 10/19/99 rodtoll Fix: Bug #113904 Shutdown Issues
* Added handler for SESSIONLOST messages
* 10/29/99 rodtoll Bug #113726 - Integrate Voxware Codecs, updating to use new
* pluggable codec architecture.
* 11/12/99 rodtoll Updated to use new playback and record classes and remove
* old playback/record system. (Includes new waveIn/waveOut support)
* rodtoll Updated to support new recording thread
* rodtoll Added new echo suppression code
* 11/17/99 rodtoll Fix: Bug #117177 - Calling Connect w/o voice session never returns
* 11/23/99 rodtoll Updated Initialize/SetNotifyMask so error checking behaviour is consistant
* 12/16/99 rodtoll Bug #117405 - 3D Sound APIs misleading - 3d sound apis renamed
* The Delete3DSoundBuffer was re-worked to match the create
* rodtoll Bug #122629 - Host migration broken in unusual configurations
* Implemented new host migration scheme
* 01/10/00 pnewson AGC and VA tuning
* 01/14/2000 rodtoll Updated for new Set/GetTransmit target parameters
* rodtoll Updated to support multiple targets
* rodtoll Updated for new notification queueing
* rodtoll Added FPM for handling notification memory
* 01/21/00 rodtoll Bug #128897 - Disconnect timeout broken
* 01/24/2000 rodtoll Bug #129427: Calling Destroy3DSoundBuffer for player who has
* already disconnected resulted in an incorrect DVERR_NOTBUFFERED
* error code.
* 01/27/2000 rodtoll Bug #129934 - Update Create3DSoundBuffer to take DSBUFFERDESC
* 02/17/2000 rodtoll Bug #133691 - Choppy audio - queue was not adapting
* Added instrumentation
* 03/28/2000 rodtoll Re-wrote nametable handling and locking -- more scalable
* 03/29/2000 rodtoll Bug #30957 - Made conversion quality setting optional
* rodtoll Bug #30753 - Added volatile to the class definition
* 04/07/2000 rodtoll Bug #32179 - Prevent registration of > 1 interface
* rodtoll Updated to use no copy sends, so handles pooling frames to be sent, proper
* pulling of frames from pools and returns.
* 05/17/2000 rodtoll Bug #35110 Simultaneous playback of 2 voices results in distorted playback
* 06/21/2000 rodtoll Bug #35767 - Implement ability for Dsound effects processing if dpvoice buffers
* Updated Connect and Create3DSoundBuffer to take buffers instead of descriptions
* rodtoll Bug #36820 - Host migrates to wrong client when server is shut down before host's client disconnects
* Caused because client attempts to register new server when there is one already
* rodtoll Bug #37045 - Race conditions prevent acknowledgement of new host
* Added send when new host is elected of settingsconfirm message
* 06/27/2000 rodtoll Fixed window where outstanding sends being returned after we have deregistered
* Voice now waits for all outstanding voice sends to complete before shutting down
* rodtoll Added COM abstraction
* 07/09/2000 rodtoll Added signature bytes
* 07/12/2000 rodtoll Bug #39117 - Access violation while running VoicePosition. Several issues:
* - Allow Destroy3DBuffer during disconnect
* - Move nametable cleanup to before freesoundbufferlist
* - Fixed code so always remove from list on DeleteSoundTarget
* - Removed unneeded logic
* 07/22/20000 rodtoll Bug #40296, 38858 - Crashes due to shutdown race condition
* Now ensures that all threads from transport have left and that
* all notificatinos have been processed before shutdown is complete.
* 11/16/2000 rodtoll Bug #40587 - DPVOICE: Mixing server needs to use multi-processors
* 01/26/2001 rodtoll WINBUG #293197 - DPVOICE: [STRESS} Stress applications cannot tell difference between out of memory and internal errors.
* Remap DSERR_OUTOFMEMORY to DVERR_OUTOFMEMORY instead of DVERR_SOUNDINITFAILURE.
* Remap DSERR_ALLOCATED to DVERR_PLAYBACKSYSTEMERROR instead of DVERR_SOUNDINITFAILURE.
* 04/06/2001 kareemc Added Voice Defense
* 04/11/2001 rodtoll WINBUG #221494 DPVOICE: Capped the # of queued recording events to prevent multiple wakeups resulting in false lockup
* detection
*
***************************************************************************/
#ifndef __DVCLIENTENGINE_H
#define __DVCLIENTENGINE_H
#define DVCSTATE_NOTINITIALIZED 0x00000000
#define DVCSTATE_IDLE 0x00000001
#define DVCSTATE_CONNECTING 0x00000002
#define DVCSTATE_CONNECTED 0x00000003
#define DVCSTATE_DISCONNECTING 0x00000004
#define DVCECHOSTATE_IDLE 0x00000000
#define DVCECHOSTATE_RECORDING 0x00000001
#define DVCECHOSTATE_PLAYBACK 0x00000002
struct DIRECTVOICECLIENTOBJECT;
// Size in bytes of the fixed size elements
#define DV_CLIENT_NOTIFY_ELEMENT_SIZE 16
// Wakeup multiplier -- how many times / time we're supposed
// to wakeup should we actually wake up
#define DV_CLIENT_WAKEUP_MULTIPLER 4
#if defined(DEBUG) || defined(DBG)
#define CHECKLISTINTEGRITY CheckListIntegrity
#else
#define CHECKLISTINTEGRITY()
#endif
// CDirectVoiceClientEngine
//
// This class represents the IDirectXVoiceClient interface.
//
// The class is thread safe except for construction and
// destruction. The class is protected with a Multiple-Reader
// Single-Write lock.
//
#define VSIG_CLIENTENGINE 'ELCV'
#define VSIG_CLIENTENGINE_FREE 'ELC_'
//
volatile class CDirectVoiceClientEngine: public CDirectVoiceEngine
{
protected:
typedef enum { NOTIFY_FIXED, // Structure stored in fixed
NOTIFY_DYNAMIC // Memory allocated in dynamic
} ElementType;
struct CNotifyElement
{
typedef VOID (*PNOTIFY_COMPLETE)(PVOID pvContext, CNotifyElement *pElement);
DWORD m_dwType;
union _Element
{
struct
{
LPVOID m_lpData;
} dynamic;
struct
{
BYTE m_bFixedHolder[DV_CLIENT_NOTIFY_ELEMENT_SIZE];
} fixed;
} m_element;
DWORD m_dwDataSize;
ElementType m_etElementType;
PVOID pvContext;
PNOTIFY_COMPLETE pNotifyFunc;
CNotifyElement *m_lpNext;
};
struct TimerHandlerParam
{
HANDLE hPlaybackTimerEvent;
volatile LONG lPlaybackCount;
HANDLE hRecordTimerEvent;
DNCRITICAL_SECTION csPlayCount;
};
public:
CDirectVoiceClientEngine( DIRECTVOICECLIENTOBJECT *lpObject );
~CDirectVoiceClientEngine();
public: // IDirectXVoiceClient Interface
HRESULT Connect( LPDVSOUNDDEVICECONFIG lpSoundDeviceConfig, LPDVCLIENTCONFIG lpClientConfig, DWORD dwFlags );
HRESULT Disconnect( DWORD dwFlags );
HRESULT GetSessionDesc( LPDVSESSIONDESC lpSessionDescBuffer );
HRESULT GetClientConfig( LPDVCLIENTCONFIG lpClientConfig );
HRESULT SetClientConfig( LPDVCLIENTCONFIG lpClientConfig );
HRESULT GetCaps( LPDVCAPS lpCaps );
HRESULT GetCompressionTypes( LPVOID lpBuffer, LPDWORD lpdwSize, LPDWORD lpdwNumElements, DWORD dwFlags );
HRESULT SetTransmitTarget( PDVID dvidTarget, DWORD dwNumTargets, DWORD dwFlags );
HRESULT GetTransmitTarget( LPDVID lpdvidTargets, PDWORD pdwNumElements, DWORD dwFlags );
HRESULT Create3DSoundBuffer( DVID dvidID, LPDIRECTSOUNDBUFFER lpdsBufferDesc, DWORD dwPriority, DWORD dwFlags, LPDIRECTSOUND3DBUFFER *lpBuffer );
HRESULT Delete3DSoundBuffer( DVID dvidID, LPDIRECTSOUND3DBUFFER *lpBuffer );
HRESULT SetNotifyMask( LPDWORD lpdwMessages, DWORD dwNumElements );
HRESULT GetSoundDeviceConfig( PDVSOUNDDEVICECONFIG pSoundDeviceConfig, PDWORD pdwBufferSize );
public: // CDirectVoiceEngine Members
HRESULT Initialize( CDirectVoiceTransport *lpTransport, LPDVMESSAGEHANDLER lpdvHandler, LPVOID lpUserContext, LPDWORD lpdwMessages, DWORD dwNumElements );
BOOL ReceiveSpeechMessage( DVID dvidSource, LPVOID lpMessage, DWORD dwSize );
HRESULT StartTransportSession();
HRESULT StopTransportSession();
HRESULT AddPlayer( DVID dvID );
HRESULT RemovePlayer( DVID dvID );
HRESULT CreateGroup( DVID dvID );
HRESULT DeleteGroup( DVID dvID );
HRESULT AddPlayerToGroup( DVID dvidGroup, DVID dvidPlayer );
HRESULT RemovePlayerFromGroup( DVID dvidGroup, DVID dvidPlayer );
HRESULT MigrateHost( DVID dvidNewHost, LPDIRECTPLAYVOICESERVER lpdvServer );
HRESULT MigrateHost_RunElection();
inline DWORD GetCurrentState() { return m_dwCurrentState; };
BOOL InitClass();
public: // packet validation
inline BOOL ValidateSessionType( DWORD dwSessionType );
inline BOOL ValidateSessionFlags( DWORD dwFlags );
inline BOOL ValidatePlayerFlags( DWORD dwFlags );
inline BOOL ValidatePlayerDVID( DVID dvid );
inline BOOL ValidatePacketType( PDVPROTOCOLMSG_FULLMESSAGE lpdvFullMessage );
protected: // Message handlers
HRESULT InternalSetNotifyMask( LPDWORD lpdwMessages, DWORD dwNumElements );
BOOL QueueSpeech( DVID dvidSource, PDVPROTOCOLMSG_SPEECHHEADER pdvSpeechHeader, PBYTE pbData, DWORD dwSize );
BOOL HandleConnectRefuse( DVID dvidSource, PDVPROTOCOLMSG_CONNECTREFUSE lpdvConnectRefuse, DWORD dwSize );
BOOL HandleCreateVoicePlayer( DVID dvidSource, PDVPROTOCOLMSG_PLAYERJOIN lpdvCreatePlayer, DWORD dwSize );
BOOL HandleDeleteVoicePlayer( DVID dvidSource, PDVPROTOCOLMSG_PLAYERQUIT lpdvDeletePlayer, DWORD dwSize );
BOOL HandleSpeech( DVID dvidSource, PDVPROTOCOLMSG_SPEECHHEADER lpdvSpeech, DWORD dwSize );
BOOL HandleSpeechWithFrom( DVID dvidSource, PDVPROTOCOLMSG_SPEECHWITHFROM lpdvSpeech, DWORD dwSize );
BOOL HandleSpeechBounce( DVID dvidSource, PDVPROTOCOLMSG_SPEECHHEADER lpdvSpeech, DWORD dwSize );
BOOL HandleConnectAccept( DVID dvidSource, PDVPROTOCOLMSG_CONNECTACCEPT lpdvConnectAccept, DWORD dwSize );
BOOL HandleDisconnectConfirm( DVID dvidSource, PDVPROTOCOLMSG_DISCONNECT lpdvDisconnect, DWORD dwSize );
BOOL HandleSetTarget( DVID dvidSource, PDVPROTOCOLMSG_SETTARGET lpdvSetTarget, DWORD dwSize );
BOOL HandleSessionLost( DVID dvidSource, PDVPROTOCOLMSG_SESSIONLOST lpdvSessionLost, DWORD dwSize );
BOOL HandlePlayerList( DVID dvidSource, PDVPROTOCOLMSG_PLAYERLIST lpdvPlayerList, DWORD dwSize );
BOOL HandleHostMigrated( DVID dvidSource, PDVPROTOCOLMSG_HOSTMIGRATED lpdvHostMigrated, DWORD dwSize );
BOOL HandleHostMigrateLeave( DVID dvidSource, PDVPROTOCOLMSG_HOSTMIGRATELEAVE lpdvHostMigrateLeave, DWORD dwSize );
friend class CClientRecordSubSystem;
protected:
void CheckListIntegrity();
void DoSessionLost(HRESULT hrReason);
void DoSignalDisconnect(HRESULT hrDisconnectReason);
void HandleThreadError( HRESULT hrResult );
// Actually send the message to the client app
void TransmitMessage( DWORD dwMessageType, LPVOID lpData, DWORD dwSize );
void Cleanup();
void DoDisconnect();
void DoConnectResponse();
void WaitForBufferReturns();
void SetCurrentState( DWORD dwState );
HRESULT InitializeSoundSystem();
HRESULT ShutdownSoundSystem();
HRESULT CheckForDuplicateObjects();
HRESULT HandleLocalHostMigrateCreate();
void SetConnectResult( HRESULT hrOriginalResult );
HRESULT GetConnectResult();
void SetupPlaybackBufferDesc( LPDSBUFFERDESC lpdsBufferDesc, LPDSBUFFERDESC lpdsBufferSource );
HRESULT InitializeClientServer();
void DeInitializeClientServer();
void CheckForUserTimeout( DWORD dwCurTime );
void SendPlayerLevels();
BOOL CheckShouldSendMessage( DWORD dwMessageType );
HRESULT CreateGeneralBuffer( );
void UpdateActivePlayPendingList();
void UpdateActiveNotifyPendingList();
void CleanupNotifyLists();
void CleanupPlaybackLists();
static void __cdecl RecordThread( void *lpParam );
static void __cdecl PlaybackThread( void *lpParam );
static void __cdecl NotifyThread( void *lpParam );
static BOOL MixingWakeupProc( DWORD_PTR param );
static PVOID ClientBufferAlloc( void *const pv, const DWORD dwSize );
static void ClientBufferFree( void *const pv, void *const pvBuffer );
PDVTRANSPORT_BUFFERDESC GetTransmitBuffer( DWORD dwSize, LPVOID *ppvContext );
HRESULT SendComplete( PDVEVENTMSG_SENDCOMPLETE pSendComplete );
void ReturnTransmitBuffer( PVOID pvContext );
HRESULT Send_ConnectRequest();
HRESULT Send_DisconnectRequest();
HRESULT Send_SessionLost();
HRESULT Send_SettingsConfirm();
HRESULT SetPlaybackVolume( LONG lVolume );
DWORD m_dwSignature;
CFramePool *m_lpFramePool; // Pool of frames
CDirectVoiceTransport *m_lpSessionTransport; // Transport for the session
DVSOUNDDEVICECONFIG m_dvSoundDeviceConfig; // Sound device config
DVCLIENTCONFIG m_dvClientConfig; // Sound general config
DVSESSIONDESC m_dvSessionDesc; // Session configuration
DVCAPS m_dvCaps; // Caps
LPDVMESSAGEHANDLER m_lpMessageHandler; // User message handler
LPVOID m_lpUserContext; // User context for message handler
DVID m_dvidServer; // DVID of the server
PDVID m_pdvidTargets; // DVID of the current target(s) (Protected by m_csTargetLock)
DWORD m_dwNumTargets; // # of targets (Protected by m_csTargetLock)
DWORD m_dwTargetVersion; // Increment each time targets are changed
DNCRITICAL_SECTION m_csTargetLock; // If you need write/read lock, always take it before this lock
HRESULT InternalSetTransmitTarget( PDVID pdvidTargets, DWORD dwNumTargets );
HRESULT CheckForAndRemoveTarget( DVID dvidID );
volatile DWORD m_dwCurrentState; // Current engine state
CFrame *m_tmpFrame; // Tmp frame for receiving
LPDIRECTPLAYVOICESERVER m_lpdvServerMigrated; // Stores reference to migrated host
DWORD m_dwActiveCount;
DWORD m_dwEchoState;
DNCRITICAL_SECTION m_lockPlaybackMode;
DNCRITICAL_SECTION m_csCleanupProtect; // Used to ensure only only cleanup instance is active
protected: // Buffer maintenance
HRESULT AddSoundTarget( CSoundTarget *lpcsTarget );
HRESULT DeleteSoundTarget( DVID dvidID );
HRESULT FindSoundTarget( DVID dvidID, CSoundTarget **lpcsTarget );
HRESULT InitSoundTargetList();
HRESULT FreeSoundTargetList();
protected: // Notification queue
// Queue up a notification for the user
HRESULT NotifyQueue_Add( DWORD dwMessageType, LPVOID lpData, DWORD dwDataSize, PVOID pvContext = NULL, CNotifyElement::PNOTIFY_COMPLETE pNotifyFunc = NULL );
HRESULT NotifyQueue_Get( CNotifyElement **pneElement );
HRESULT NotifyQueue_Init();
HRESULT NotifyQueue_Free();
HRESULT NotifyQueue_ElementFree( CNotifyElement *lpElement );
HRESULT NotifyQueue_IndicateNext();
void NotifyQueue_Disable();
void NotifyQueue_Enable();
void NotifyQueue_Flush();
static void NotifyComplete_SyncWait( PVOID pvContext, CNotifyElement *pElement );
static void NotifyComplete_LocalPlayer( PVOID pvContext, CNotifyElement *pElement );
static void NotifyComplete_RemotePlayer( PVOID pvContext, CNotifyElement *pElement );
DNCRITICAL_SECTION m_csNotifyQueueLock; // Notification queue
BOOL m_fNotifyQueueEnabled;
CNotifyElement *m_lpNotifyList; // List of notifications
HANDLE m_hNewNotifyElement; // Semaphore signalled when new event is queued
HRESULT SendConnectResult();
HRESULT SendDisconnectResult();
protected:
HRESULT SetupInitialBuffers();
HRESULT SetupSpeechBuffer();
HRESULT FreeBuffers();
protected: // Statistics
void ClientStats_Reset();
void ClientStats_Begin();
void ClientStats_End();
void ClientStats_Dump();
void ClientStats_Dump_Record();
void ClientStats_Dump_Playback();
void ClientStats_Dump_Receive();
void ClientStats_Dump_Transmit();
ClientStatistics m_stats;
ClientStatistics *m_pStatsBlob;
protected: // Sound System Information
CAudioPlaybackBuffer *m_audioPlaybackBuffer; // Audio playback buffer
CAudioRecordDevice *m_audioRecordDevice; // Audio record device
CAudioPlaybackDevice *m_audioPlaybackDevice; // Audio Playback Device
CAudioRecordBuffer *m_audioRecordBuffer; // Audio Record Buffer;
HANDLE m_hNotifyDone; // Signalled when notify thread dies
HANDLE m_hNotifyTerminate; // Signalled to terminate notify thread
HANDLE m_hNotifyChange; // Signalled when notification interval changes
HANDLE m_hRecordDone; // Record thread done signal
HANDLE m_hRecordTerminate; // Record thread stop signal
HANDLE m_hPlaybackDone; // Playback thread done signal
HANDLE m_hPlaybackTerminate; // Playback thread terminate signal
HANDLE m_hConnectAck; // Signalled when connect complete
HANDLE m_hDisconnectAck; // Signalled when disconnect complete
HRESULT m_hrDisconnectResult; // Result of the disconnect result
HRESULT m_hrConnectResult; // Result of the connection request
HRESULT m_hrOriginalConnectResult; // Original Connect Result (Untranslated)
DVID m_dvidLocal; // Local DVID
DNCRITICAL_SECTION m_csClassLock;
BOOL m_fCritSecInited;
protected:
// Compression Control Data
LPDVFULLCOMPRESSIONINFO m_lpdvfCompressionInfo; // Information about current compression type
// Memory pointed to is owned by dvcdb.cpp
DWORD m_dwCompressedFrameSize;
// Size of a compressed frame
DWORD m_dwUnCompressedFrameSize;
// Size of an uncompressed frame
DWORD m_dwNumPerBuffer; // # of frames / directsound buffer
CFramePool *m_pFramePool; // Frame pool
DIRECTVOICECLIENTOBJECT *m_lpObject; // Cached pointer to the COM interface
DWORD m_dwSynchBegin; // GetTickCount at Connect/Disconnect start
HANDLE m_hNotifySynch; // Notified when connect/disconnect completes
HANDLE m_hNotifyDisconnect; // Signalled
HANDLE m_hNotifyConnect; // Connect
HANDLE m_hRecordThreadHandle;
HANDLE m_hPlaybackThreadHandle;
DNCRITICAL_SECTION m_csBufferLock; // Lock to protect playback mixing buffers
CSoundTarget *m_lpstBufferList; // Other Playback Mixing Buffers
CSoundTarget *m_lpstGeneralBuffer; // General Playback Mixing Buffer
LPDWORD m_lpdwMessageElements; // Buffer with notifiers
DWORD m_dwNumMessageElements; // Number of notifiers
DNCRITICAL_SECTION m_csNotifyLock; // Lock on list of notifiers
DSBUFFERDESC m_dsBufferDesc;
TimerHandlerParam m_thTimerInfo;
Timer *m_pTimer;
DWORD m_dwLastConnectSent;
DWORD m_dwHostOrderID;
DWORD m_dwMigrateHostOrderID; // Host Order ID of current host
PVOID m_pvLocalPlayerContext;
DIRECTSOUNDMIXER_SRCQUALITY m_dwOriginalRecordQuality;
DIRECTSOUNDMIXER_SRCQUALITY m_dwOriginalPlayQuality;
PFPOOL m_pfpNotifications; // Frame pool for notifications
BYTE m_bLastPeak; // Last frame peak
BYTE m_bLastPlaybackPeak; // Last peak on playback
BYTE m_bMsgNum; // Last msg # transmitted
BYTE m_bSeqNum; // Last sequence # transmitted
BOOL m_bLastTransmitted; // Was last frame sent?
BOOL m_fSessionLost; // Flag indicating session was lost
BOOL m_fLocalPlayerNotify; // Has notification been sent for local player
BOOL m_fLocalPlayerAvailable;
BOOL m_fConnectAsync; // Are we connecting async?
BOOL m_fDisconnectAsync; // Are we disconnecting async?
CVoiceNameTable m_voiceNameTable;
DWORD m_dwPlayActiveCount;
BILINK m_blPlayActivePlayers;
BILINK m_blPlayAddPlayers;
DNCRITICAL_SECTION m_csPlayAddList;
BILINK m_blNotifyActivePlayers;
BILINK m_blNotifyAddPlayers;
DNCRITICAL_SECTION m_csNotifyAddList;
CLockedFixedPool<CVoicePlayer> m_fpPlayers;
DNCRITICAL_SECTION m_csTransmitBufferLock;
PFPOOL m_pBufferDescPool;
PFPOOL *m_pBufferPools;
DWORD *m_pdwBufferPoolSizes;
DWORD m_dwNumPools;
PERF_APPLICATION m_perfInfo; // Perf info entry for this object
PERF_APPLICATION_INFO m_perfAppInfo;
};
#endif