www.pudn.com > CP_IVR.zip > HSound.cpp
/*****************************************************************
* HSound.cpp: implementation of the CHSound class.
*
* WARNINGS:
* - Just 1 and only 1 instance of this class must be
* created.
*
*
* Auther: Hamed.M.
* eMail : HamedMosavi @ hotmail.com
* HamedMosavi @ gmail.com
*****************************************************************/
#include "stdafx.h"
#include "HSound.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
#define SET 0
#define KILL 1
DWORD CHSound::m_dwStatus = STT_ASLEEP;
BOOL CHSound::m_bContinue = TRUE;
BOOL CHSound::m_bThreadContinue = TRUE;
HANDLE CHSound::m_hStatEvent = NULL;
LPWAVEHDR CHSound::m_lpWHdr = NULL;
/*****************************************************************
* Constructor
*****************************************************************/
//HANDLE CHSound::s_hEvent = NULL;
CHSound::CHSound()
{
InitializeCriticalSection(&m_statusCrtcSec);
InitializeCriticalSection(&m_externalResetCrtcSec);
m_hStatEvent = CreateEvent(NULL, TRUE/*ManualReset*/, FALSE, NULL);
m_bThreadContinue = TRUE; /* Don't exit the thread */
/* Run a thread to cleanup wave buffer */
m_pCallBackThread = AfxBeginThread (
CallBackThread,
(LPVOID) this,
THREAD_PRIORITY_NORMAL);
m_dwMinBufSize = 10240;//61440;//10240;//5120; // = 5 KBytes
m_bExitAfter = FALSE;
m_bForceClose = FALSE;
m_sndIndex = 0;
m_wndTimer = NULL;
m_dwDevId = 0;
m_playList.Reset();
}
/*****************************************************************
* Destructor
*****************************************************************/
CHSound::~CHSound()
{
m_bThreadContinue = FALSE;
DeleteCriticalSection(&m_statusCrtcSec);
DeleteCriticalSection(&m_externalResetCrtcSec);
}
/*****************************************************************
* Plays a given file, to the given device
*****************************************************************/
BOOL CHSound::Play(DWORD dwDevId, LPCTSTR szWaveFile)
{
if (GetStatus() != STT_ASLEEP)
return FALSE;
SetStatus(STT_PREPARE);
m_dwDevId = dwDevId;
HPSTR lpData = NULL; /* Pointer to Wave data */
LPWAVEFORMATEX lpWaveFormatEx = NULL;
MMRESULT mmRes;
LPWAVEHDR lpWaveHdr = NULL;
m_dwBufCnt = 0;
m_dwCurrtBufIndex=0;
m_dwDataSize = 0;
SetStatus(STT_FILEOPN);
lpData = Open( szWaveFile, /* Open & read WaveFile */
lpWaveFormatEx,
m_dwDataSize);
SetStatus(STT_PREPARE);
if ( NULL == lpData ) { /* File opening error */
goto CLEAN_EXIT;
}
if (!m_bContinue) {
goto CLEAN_EXIT;
}
/* Allocate a buffer for header */
lpWaveHdr = (LPWAVEHDR) calloc (1,(DWORD)sizeof(WAVEHDR));
if (! lpWaveHdr) { /* Can't allocate memory */
goto CLEAN_EXIT;
}
if (!m_bContinue) {
goto CLEAN_EXIT;
}
if (m_dwDataSize>m_dwMinBufSize) {
m_dwBufCnt = m_dwDataSize / m_dwMinBufSize;
// a 13 KB file for example
if ( (m_dwBufCnt*m_dwMinBufSize) < m_dwDataSize)
m_dwBufCnt++;
lpWaveHdr->dwBufferLength = m_dwMinBufSize;
m_dwBLength = ( (m_dwDataSize-m_dwMinBufSize)>m_dwMinBufSize)?m_dwMinBufSize:(m_dwDataSize-m_dwMinBufSize);
m_lpBuf2 = lpData;
m_lpBuf2 += m_dwMinBufSize;
} else {
lpWaveHdr->dwBufferLength = m_dwDataSize;
}
m_lpWHdr = lpWaveHdr;
lpWaveHdr->lpData = lpData;
m_lpData = lpData;
SetStatus(STT_OPENING);
/* Open the device, and set callback function */
mmRes = waveOutOpen (
&m_hWaveOut,
dwDevId,
lpWaveFormatEx,
(DWORD)m_pCallBackThread->m_nThreadID,
0L,
CALLBACK_THREAD | WAVE_MAPPED);
/* Can't Open specified device */
if ( MMSYSERR_NOERROR != mmRes ) {
SetStatus(STT_CLOSING);
waveOutClose(m_hWaveOut);
return FALSE;
}
free(lpWaveFormatEx);
lpWaveFormatEx = NULL;
if (!m_bContinue) {
SetStatus(STT_CLOSING);
waveOutClose(m_hWaveOut);
goto CLEAN_EXIT;
}
/* Prepare header */
mmRes = waveOutPrepareHeader(
m_hWaveOut,
lpWaveHdr,
sizeof (WAVEHDR) );
/* Can't Prepare header */
if ( MMSYSERR_NOERROR != mmRes ) {
SetStatus(STT_CLOSING);
waveOutClose(m_hWaveOut);
goto CLEAN_EXIT;
}
if (!m_bContinue) {
SetStatus(STT_RSTTING);
waveOutUnprepareHeader(m_hWaveOut,lpWaveHdr,sizeof(WAVEHDR));
SetStatus(STT_CLOSING);
waveOutClose(m_hWaveOut);
goto CLEAN_EXIT;
}
/* Modem's are weak! Loud as much as possible */
waveOutSetVolume(m_hWaveOut,0xFF00FF00);
SetStatus(STT_PLAYING);
/* Write a block of data to the device */
mmRes = waveOutWrite(
m_hWaveOut,
lpWaveHdr,
sizeof(WAVEHDR) );
/* Can't Do the last job :(( */
if ( MMSYSERR_NOERROR != mmRes ) {
SetStatus(STT_RSTTING);
waveOutUnprepareHeader(m_hWaveOut,lpWaveHdr,sizeof(WAVEHDR));
SetStatus(STT_CLOSING);
waveOutClose(m_hWaveOut);
goto CLEAN_EXIT;
}
if (m_dwDataSize>m_dwMinBufSize) {
m_lpWHdr->lpData = m_lpBuf2;
m_lpWHdr->dwBufferLength = m_dwBLength;
m_dwCurrtBufIndex++;
}
/* Playback started, events will be received by callback
function, then it will notify thread to cleanup. */
return TRUE;
CLEAN_EXIT:;
SetStatus(STT_RSTTING);
if (lpData)
free(lpData);
if (lpWaveFormatEx)
free(lpWaveFormatEx);
if (lpWaveHdr)
free(lpWaveHdr);
SetStatus(STT_ASLEEP);
return FALSE;
}
/*****************************************************************
* Opens a wave file, buffers wave data
* Returns a pointer to buffer
*****************************************************************/
HPSTR CHSound::Open(LPCTSTR szWaveFile, LPWAVEFORMATEX& lpWaveFmt, DWORD& dwDataSize)
{
HMMIO hmmio;
MMCKINFO mmckinfoParent;
MMCKINFO mmckinfoSubchunk;
DWORD dwFmtSize = 0;
hmmio = mmioOpen((unsigned short*) /* Open Wave file */
szWaveFile, NULL, MMIO_READ | MMIO_ALLOCBUF);
if(!hmmio) /* Can't open */
{
return NULL;
}
if (!m_bContinue)
{
mmioClose(hmmio, 0);
return NULL;
}
/* Make sure file format is Wave */
mmckinfoParent.fccType = MAKEFOURCC('W', 'A', 'V', 'E');
if (mmioDescend(hmmio, (LPMMCKINFO) &mmckinfoParent,
NULL, MMIO_FINDRIFF))
{ /* File format is NOT 'Wave' */
mmioClose(hmmio, 0);
return NULL;
}
if (!m_bContinue)
{
mmioClose(hmmio, 0);
return NULL;
}
/* Make sure there is a 'fmt ' chunk */
mmckinfoSubchunk.ckid = MAKEFOURCC('f', 'm', 't', ' ');
if (mmioDescend(hmmio, (LPMMCKINFO)&mmckinfoSubchunk,
(LPMMCKINFO)&mmckinfoParent, MMIO_FINDCHUNK))
{ /* No 'fmt ' chunk */
mmioClose(hmmio, 0);
return NULL;
}
if (!m_bContinue)
{
mmioClose(hmmio, 0);
return NULL;
}
/* Find size of the 'fmt ' chunk */
dwFmtSize = mmckinfoSubchunk.cksize;
/* Allocate memory for format chunk */
lpWaveFmt = (LPWAVEFORMATEX) calloc (1, dwFmtSize);
// HeapAlloc(m_hHeap, HEAP_ZERO_MEMORY, dwFmtSize);
if (!lpWaveFmt) /* Can't alocate memory */
{
mmioClose(hmmio, 0);
return NULL;
}
if (!m_bContinue)
{
free(lpWaveFmt);
mmioClose(hmmio, 0);
return NULL;
}
/* Read 'fmt ' chunk */
if (mmioRead(hmmio, (HPSTR) lpWaveFmt, dwFmtSize) !=
(LONG) dwFmtSize)
{ /* Corrupt 'fmt ' chunk */
free(lpWaveFmt);
mmioClose(hmmio, 0);
return NULL;
}
if (!m_bContinue)
{
free(lpWaveFmt);
mmioClose(hmmio, 0);
return NULL;
}
/* Ascend out of the 'fmt ' subchunk */
mmioAscend(hmmio, &mmckinfoSubchunk, 0);
/* Find the data subchunk */
mmckinfoSubchunk.ckid = MAKEFOURCC('d', 'a', 't', 'a');
if (mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent,
MMIO_FINDCHUNK))
{ /* No data subchunk! */
free(lpWaveFmt);
mmioClose(hmmio, 0);
return NULL;
}
/* Get the size of the data subchunk */
dwDataSize = mmckinfoSubchunk.cksize;
if ( 0L == dwDataSize ) /* No data in the data subchunk */
{
free(lpWaveFmt);
mmioClose(hmmio, 0);
return NULL;
}
if (!m_bContinue)
{
free(lpWaveFmt);
mmioClose(hmmio, 0);
dwDataSize = 0;
return NULL;
}
/* Allocate memory for data, no need to lock */
HPSTR lpData = (HPSTR) calloc (1, dwDataSize );
// HeapAlloc(m_hHeap, HEAP_ZERO_MEMORY, dwDataSize);
/* Read the waveform data subchunk */
if(mmioRead(hmmio, (HPSTR) lpData, dwDataSize)
!= (LONG) dwDataSize)
{
free(lpWaveFmt);
free(lpData);
mmioClose(hmmio, 0);
dwDataSize = 0;
return NULL;
}
/* We're done with the file, let's close it */
mmioClose(hmmio, 0);
if (!m_bContinue)
{
free(lpWaveFmt);
free(lpData);
dwDataSize = 0;
return NULL;
}
return lpData;
}
/*****************************************************************
* Pauses playing
*****************************************************************/
BOOL CHSound::Pause()
{
return FALSE;
}
/*****************************************************************
* Stops playing
*****************************************************************/
BOOL CHSound::Stop()
{
return FALSE;
}
/*****************************************************************
* Closes the device
*****************************************************************/
BOOL CHSound::Close()
{
m_bContinue = FALSE;
m_bForceClose = TRUE;
SetStatus(STT_RSTTING);
MMRESULT mmRes = waveOutReset(m_hWaveOut);
int ptcn = 0;
/* If we were playng */
if (mmRes==0) {
while(GetStatus()!=STT_ASLEEP) {
WaitForSingleObject(m_hStatEvent,100);
if (ptcn++>10) break;
ResetEvent(m_hStatEvent);
}
// return TRUE;
}
KillTimer();
CleanPlayList();
SetStatus(STT_ASLEEP);
return TRUE;
}
/*****************************************************************
* Indicates wheather this device is playing a sound or not
*****************************************************************/
BOOL CHSound::IsPlaying()
{
if (GetStatus()==STT_PLAYING)
return TRUE;
return FALSE;
}
/*****************************************************************
* Thread to receive and process sound messages
******************************************************************
*
* MSDN says it's not possible to do waveOutUnprepareHeader in the
* callback function, so I do it here + I do cleanup.
*
*****************************************************************/
UINT CHSound::CallBackThread(LPVOID pParam)
{
CHSound* pParent = (CHSound*) pParam;
MSG msg;
while (m_bThreadContinue) {
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
/* One file playback is done */
if ( msg.message==MM_WOM_DONE ) {
if (msg.lParam)
pParent->PlayNextBuffer(msg);/* Inform parent */
}
} else Sleep(5);
}
return 0;
}
void CHSound::SetStatus(DWORD dwCurStat)
{
EnterCriticalSection(&m_statusCrtcSec);
m_dwStatus ^= m_dwStatus;
m_dwStatus |= dwCurStat;
LeaveCriticalSection(&m_statusCrtcSec);
SetEvent(m_hStatEvent);
}
DWORD CHSound::GetStatus()
{
return m_dwStatus;
}
void CHSound::PlayNextBuffer(MSG msg)
{
if (!m_bContinue) {
goto PNB_EXIT;
// Any more buffers to read?
} else if (m_dwCurrtBufIndexlpData = m_lpBuf2;
m_lpWHdr->dwBufferLength = m_dwBLength;
} else {
// m_dwCurrtBufIndex = 0;
// m_dwBufCnt = 0;
goto PNB_EXIT;
}
return;
PNB_EXIT:;
SetStatus(STT_RSTTING);
m_lpWHdr->dwBufferLength = m_dwDataSize;
m_lpWHdr->lpData = m_lpData;
waveOutUnprepareHeader(
m_hWaveOut,
m_lpWHdr,
sizeof(WAVEHDR));
SetStatus(STT_CLOSING);
/* close wave device */
waveOutClose((HWAVEOUT)msg.wParam);
/* free the wave data */
SetStatus(STT_RSTTING);
free (m_lpData);
free (m_lpWHdr); /* free the header */
SetStatus(STT_ASLEEP);
if (m_bContinue)
PlayNextFile();
}
BOOL CHSound::PlayNextFile()
{
if (!m_bContinue) return FALSE;
int patience = 0,retryP = 0;
CString str = _T("");
retry:;
patience = 0;
/* Give me next file to play */
/* If, for any reason, FileName is empty */
while (str==_T("")) {
str = m_playList.GetNext();
if (++patience>1000) return FALSE;
if (!m_bContinue) return FALSE;
}
/* If play list is finished */
if ( str == _T("END_OF_LIST") ) {
if (m_bExitAfter) {
KillTimer();
PostThreadMessage(m_boss,WM_SOUND_DONE,1,0);
} else {
/* Playback ended, so set a timer to */
SetTimer(); /* restart playing last menu */
} /* in an interval */
return TRUE;
} else if ( str==_T("DELAY")) {
if (!m_bContinue) return FALSE;
Sleep(500);
str = _T("");
patience = 0;
goto retry;
}
/* We are going to play, so tell the timer to shut up! */
KillTimer();
/* Start playback */
if (!Play( m_dwDevId, str )) {
if (!m_bContinue) return FALSE;
else {
TRACE(L"Cant play [%s] \n",str);
/* Skip this file if can't play */
if (++retryP>10) {
retryP = 0;
str=_T("");
}
// Sleep(100);
goto retry;
}
}
return TRUE;
}
/*****************************************************************
* We need a window to start or kill a timer. I'm too lazy writing
* my own class!
******************************************************************
*
* WARNING: If you need a timer, rhis function MUST be called
* In a class that has a pointer to a dialog, or any
* type of a window, & the window MUST process Timer
* events. Look at DaliaDlg here.
*
*****************************************************************/
void CHSound::SetTimerWindow(CWnd *wndTimer)
{
m_wndTimer = wndTimer;
}
/*****************************************************************
* Sets a timer
*****************************************************************/
void CHSound::SetTimer()
{
if (m_wndTimer) {
if (m_wndTimer->GetSafeHwnd())
m_wndTimer->SendMessage(
WM_S_TIMER, SET,0);
}
}
/*****************************************************************
* Kills the timer, though No more notifications
*****************************************************************/
void CHSound::KillTimer()
{
if (m_wndTimer) {
if (m_wndTimer->GetSafeHwnd())
m_wndTimer->SendMessage(
WM_S_TIMER, KILL,0);
}
}
/*****************************************************************
* Start playing 'play list'
*****************************************************************/
BOOL CHSound::PlayList(DWORD dwDeviceId, BOOL bExitAfter, BOOL bDisableForceExit)
{
if (m_bForceClose && (!bDisableForceExit) ) return FALSE;
if (bDisableForceExit) {
m_bForceClose = FALSE;
}
m_dwDevId = dwDeviceId;
m_bExitAfter = bExitAfter;
m_bContinue = TRUE;
/* Now start playing new list */
return PlayNextFile();
}
/*****************************************************************
* Adds a file to our 'play list'
*****************************************************************/
void CHSound::AddToPlayList(CString szWaveFile)
{
m_playList.Add(szWaveFile);
}
/*****************************************************************
* Removes all files from the 'play list'
*****************************************************************/
void CHSound::CleanPlayList()
{
m_playList.Reset();
}
/*****************************************************************
* Makes sound class ready to get new inputs
*****************************************************************/
BOOL CHSound::Reset()
{
EnterCriticalSection(&m_externalResetCrtcSec);
if (GetStatus()==STT_EXTRNRS ||
!m_bContinue) {
LeaveCriticalSection(&m_externalResetCrtcSec);
return FALSE;
}
LeaveCriticalSection(&m_externalResetCrtcSec);
m_bContinue = FALSE;
SetStatus(STT_EXTRNRS);
MMRESULT mmRes = waveOutReset(m_hWaveOut);
int ptcn = 0;
/* If we were playng */
if (mmRes==0) {
while(GetStatus()!=STT_ASLEEP) {
WaitForSingleObject(m_hStatEvent,100);
if (ptcn++>10) break;
ResetEvent(m_hStatEvent);
}
// return TRUE;
}
KillTimer();
CleanPlayList();
SetStatus(STT_ASLEEP);
return TRUE;
}
/*****************************************************************
* Set a thread ID and use it to talk to parent manager
*****************************************************************/
void CHSound::SetManager(unsigned long mngrThread)
{
m_boss = mngrThread;
}