CWaveBox - WAI 包装器,用于播放 PCM 多波形,在游戏开发中很有用






4.80/5 (16投票s)
2005年4月15日
9分钟阅读

172502

4766
多波形播放器 ('波形音频接口' PCM 波形包装器)。
引言
您是否曾希望在 Windows 应用程序或游戏中播放波形,而不被 SDK / MFC 的异常情况所困扰,这些异常情况只给您提供了部分功能,如果您想做一些严肃的工作,这些功能实际上是无用的?在这里,我将收集所有需要的组件并将其升级到更高的水平,以便您可以专注于更严肃的音频工作。
CWaveBox
类依赖于 SDK 的“波形音频接口”API 来同时播放/生成 (PCM 未压缩) 的多波形。在游戏中,需要混合和计时多个波形,以便为交互式特殊/三维音效添加内容。该类通过 Windows WAI 驱动程序为您提供了这种可能性,它仅通过一个工作线程来包装 WAI 驱动程序以支持多波形。我见过很多关于这个主题的例子,它们集中于连续排队/流式传输 WAI 的两个或多个波形数据块,以避免在切换时出现间隙,但其中只有少数或只有其中一个正确地释放/关闭了 WAI 接口,这至少会导致内存浪费无法停止。使用 CWaveBox
不会出现此类(或其他类型)的内存浪费/泄漏。通过缓存波形并从内存播放来提高播放性能,因此,该类不适用于从文件“即时”播放,这会花费额外的时间进行“播放时缓存”,而 CPU 时间并不关键。如果您愿意,请随意重新编码以实现此目的,我不会生气 ;-)。该类完全可移植到 Windows CE,因此您也可以在 Pocket PC 中播放。
背景
编码此类程序的整个想法源于“需要比 PlaySound
”、“sndPlaySound
”或任何其他部分解决方案更好的声音。此类已在一个具有出色 AI 的棋盘游戏中实现,该游戏适用于 Pocket PC (由我和我的朋友 Vrx 编写)。特别感谢那些将游戏教程翻译成八种世界语言的人,其中一些人“卡在翻译中” ;-)。这是一个关于此主题的精彩教程:使用 Windows waveOut 接口。
特别感谢编写和编码教程以及 WinAmp waveOut
插件的作者。我希望他不会批评我,因为我从他的教程/代码中借用了一些函数 (allocateBlocks
, freeBlocks
和 waveOutProc
) 以及一些变量表示法。我就是个懒鬼。;-)
演示中混合的所有波形均从 此处 下载。
代码,简要概述
CWaveBox
类使用 WAVE 模型进行结构化,该模型用于存储由 Load
方法加载的波形 data
、size
、wfx
(格式信息) 以及由 Play
方法设置的波形消息 WMSG
,以提供具有所有必需信息的 INTERFACE 模型,并作为 WAI 驱动程序的子接口服务于 PlayThread
,以成功建立 WAI 实例并为每个波形进行播放。
WaveBox.h
// WaveBox.h: interface for the CWave class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_WAVEBOX_H__DE24CFE1_7501_4DA3_AF18_667A845AAE49__INCLUDED_) #define AFX_WAVEBOX_H__DE24CFE1_7501_4DA3_AF18_667A845AAE49__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// //// //// by Zen '05 //// //// WaveBox class v0.95 //// ~~~~~~~~~~~~~~~~~~~~~~ //// ( PCM multiwave player ) //// play & joy //// //// //// //// /// precompiler #include <windows.h> #include <mmsystem.h> /// wave & PCM marks #define WAVE_FILE_MARK "RIFF" #define WAVE_HEAD_MARK "WAVEfmt " #define WAVE_DATA_MARK "data" #define WAVE_PCM_16 16 #define WAVE_PCM_1 1 /// wfx header offsets #define OFFSET_FILE_LEFT 4 #define OFFSET_HEAD_MARK 8 #define OFFSET_WAVE_PCM1 16 #define OFFSET_WAVE_PCM2 20 #define OFFSET_CHANNELS 22 #define OFFSET_SAMPLESPERSEC 24 #define OFFSET_AVGBYTESPERSEC 28 #define OFFSET_BLOCKALIGN 32 #define OFFSET_BITSPERSAMPLE 34 #define OFFSET_DATA_MARK 36 #define OFFSET_DATA_SIZE 40 #define OFFSET_WAVEDATA 44 #define HEADER_SIZE OFFSET_WAVEDATA #define EOF_EXTRA_INFO 60 /// messages typedef unsigned int WMsg; // wave messages typedef unsigned int TMsg; // thread messages #define WMSG_WAIT 0 // wave wait #define WMSG_START 1 // wave play #define TMSG_ALIVE 1 // thread alive #define TMSG_CLOSE 0 // thread close #define INT_FREE 0 // interface free #define INT_USED 1 // interface used #define THREAD_EXIT 0xABECEDA // thread exit code /// performance predefines #define SUPPORT_WAVES 10 // predefined wave count #define SUPPORT_INTERFACES 10 // predefined interface count /// buffering #define READ_BLOCK 8192 // read wave ( file ) block size #define BLOCK_SIZE 8192 // queue block size #define BLOCK_COUNT 20 // queue block count #define BP_TURN 1 // blocks per turn static CRITICAL_SECTION cs; // critical section static unsigned int __stdcall PlayThread( LPVOID lp ); // main play thread static void CALLBACK waveOutProc( HWAVEOUT hWaveOut, // waveOut prototype UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); class CWaveBox { struct WAVE { char *data; /// wave unsigned long size; /// size WAVEFORMATEX wfx; /// wfx WMsg WMSG; /// { 0,1 } wait / play }; struct INTERFACE { /// interface HWAVEOUT dev; /// device handle unsigned int state; /// { 0,1 } free / used /// wave WAVE *wave; /// current wave /// wave interface unsigned long wpos; /// current play position WAVEHDR* wblock; /// wave block volatile int wfreeblock; /// free blocks left int wcurrblock; /// current block }; public: /// members INTERFACE I[SUPPORT_INTERFACES]; // interface(s) WAVE W[SUPPORT_WAVES]; // wave(s) unsigned int wload; // current wave(s) loaded TMsg TMSG; // thread msg { 1,0 } alive / close /// prototypes int Load( TCHAR *file ); // load wave into WaveBox int Play( unsigned int wave ); // play n wave from WaveBox ( starts play thread ) CWaveBox(); // play thread created suspended virtual ~CWaveBox(); // play thread terminated /// waveOut open & close int AddInterface( HWAVEOUT *dev, /// push wave wfx to interface WAVEFORMATEX *wfx, volatile int *wfreeblock ); int RemoveInterface( HWAVEOUT dev ); /// pull wave wfx from interface protected: /// thread HANDLE thread; /// play thread handle unsigned int run; /// suspended / resumed /// prototypes /// alloc heap for wave header blocks WAVEHDR* allocateBlocks( int size, int count ); /// free heap void freeBlocks( WAVEHDR* blockArray ); }; #endif // !defined(AFX_WAVEBOX_H__DE24CFE1_7501_4DA3_AF18_667A845AAE49__INCLUDED_)
CWaveBox()
在构造函数中,PlayThread
以挂起模式创建,因此将在第一次播放时恢复。所有支持的接口都设置为初始状态,并且波形块头在堆上创建 (排队块)。将 LPVOID
参数类实例传递,以便 PlayThread
可以轻松访问类中的任何公共方法/成员。
CWaveBox::CWaveBox() { // init wave(s) counter wload = 0; // thread suspended < used for resuming thread at first play > run = 0; // create suspended player thread thread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)PlayThread, (LPVOID)this, CREATE_SUSPENDED, NULL ); // alloc mem for interface(s) for( unsigned int i = 0; i < SUPPORT_INTERFACES; i++ ) { I[i].wblock = allocateBlocks( BLOCK_SIZE, BLOCK_COUNT ); I[i].wfreeblock = BLOCK_COUNT; I[i].wcurrblock = 0; I[i].state = INT_FREE; I[i].wpos = 0; } // init msg for( i = 0; i < SUPPORT_WAVES; i++ ) W[i].WMSG = WMSG_WAIT; // init cs InitializeCriticalSection( &cs ); }
Load (加载)
首先,从文件中读取波形头,并必须通过多项验证以满足“PCM 波形”模型。然后,填充波形 wfx
结构,并将整个 data
块加载到内存中。
注意:在开始 Play
方法之前,加载所有 (您想播放的) 波形,因为在 PlayThread
恢复并从第一次 Play
开始播放时,不支持额外的 Load
。(请参阅 wload
,它也在 PlayThread
中使用,并且不受关键代码段保护。)
int CWaveBox::Load( TCHAR *file ) { if( wload == SUPPORT_WAVES ) return -1; HANDLE hFile; // open file if((hFile = CreateFile( file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL )) == INVALID_HANDLE_VALUE) return -1; // read wave header char header[HEADER_SIZE]; unsigned long rbytes = 0; if( !ReadFile(hFile, header, sizeof(header), &rbytes, NULL) ) { CloseHandle(hFile); return -1; } if( !rbytes || rbytes < sizeof(header) ) { CloseHandle(hFile); return -1; } /// check if this is a wave file if( strncmp( header, WAVE_FILE_MARK, strlen( WAVE_FILE_MARK )) ) { CloseHandle(hFile); return -1; } if( strncmp( header + OFFSET_HEAD_MARK, WAVE_HEAD_MARK, strlen( WAVE_HEAD_MARK )) ) { CloseHandle(hFile); return -1; } /// check if wave is uncompressed PCM format if ( ((*(DWORD*)(header + OFFSET_WAVE_PCM1)) != WAVE_PCM_16 ) || ((*(WORD *)(header + OFFSET_WAVE_PCM2)) != WAVE_PCM_1 )) {CloseHandle(hFile); return -1; } /// check for 'data' mark if( !strncmp( header + OFFSET_DATA_MARK, WAVE_DATA_MARK, strlen( WAVE_DATA_MARK )) ) W[wload].size = *((DWORD*)(header + OFFSET_DATA_SIZE )); /* size of data */ else { /// if data block size cant be read /// try to predict data block without extra info /// this is unusualy case W[wload].size = *((DWORD*)(header + OFFSET_FILE_LEFT )); W[wload].size -= ( HEADER_SIZE - EOF_EXTRA_INFO ); /* size of data */ } // fill WAVEFORMATEX from wave header W[wload].wfx.nSamplesPerSec = *((DWORD*)(header + OFFSET_SAMPLESPERSEC )); /* sample rate */ W[wload].wfx.wBitsPerSample = *((WORD *)(header + OFFSET_BITSPERSAMPLE )); /* sample size */ W[wload].wfx.nChannels = *((WORD *)(header + OFFSET_CHANNELS )); /* channels */ W[wload].wfx.cbSize = 0; /* size of _extra_ info */ W[wload].wfx.wFormatTag = WAVE_FORMAT_PCM; W[wload].wfx.nBlockAlign = *((WORD *)(header + OFFSET_BLOCKALIGN )); W[wload].wfx.nAvgBytesPerSec = *((DWORD*)(header + OFFSET_AVGBYTESPERSEC)); // get mem for wave data block if((W[wload].data = ( char *) calloc( W[wload].size, sizeof( char ))) == NULL) { CloseHandle(hFile); return -1; } char buffer[READ_BLOCK]; unsigned long size = W[wload].size; unsigned long read_block = 0; rbytes = 0; do /// copy uncompressed PCM wave data block { if( ( size -= rbytes ) >= READ_BLOCK ) read_block = READ_BLOCK; else if( size && size < READ_BLOCK ) read_block = size; else break; if( !ReadFile(hFile, buffer, read_block, &rbytes, NULL) ) break; if( rbytes == 0 ) break; if( rbytes < sizeof(buffer) ) memset(buffer + rbytes, 0, sizeof(buffer) - rbytes); memcpy( &W[wload].data[W[wload].size - size], buffer, rbytes ); }while( 1 ); // close file handle CloseHandle(hFile); // return current wave count return ++wload; }
播放
Play
方法非常简单,它所做的就是为作为参数传递的每个 wave
索引设置 WMSG_START
,以便 PlayThread
可以开始工作。只有在第一次开始 Play
时,PlayThread
才会恢复,并设置线程消息 TMSG_ALIVE
。
int CWaveBox::Play( unsigned int wave ) { // check wave id if( wave < 0 || wave >= wload ) return -1; // set play message EnterCriticalSection(&cs); W[wave].WMSG = WMSG_START; LeaveCriticalSection(&cs); // resume thread < at first play > if( !run ){ run = 1; TMSG = TMSG_ALIVE; ResumeThread( thread ); } return 1; }
队列模型 (流式传输多个波形)
PlayThread
模型的工作方式是充当 WAI 服务器,为当前 WAI 队列准备和发送尽可能多的 BLOCK_SIZE
的缓冲区,数量受 BLOCK_COUNT
常量的限制。可以有 SUPPORT_INTERFACES
个队列或 WAI 驱动程序线程在工作。例如,如果一个 1 秒的波形被播放 10 次,每次 Play
之间有 10ms 的延迟,PlayThread
将波形与 10 个不同的 INTERFACE
链接起来,以便播放该波形 10 次。每个线程 INTERFACE
服务于当前播放与当前 INTERFACE
链接的波形所需的每个 WAI 队列/线程。因此,最终效果将是新的延迟波形,大约 1.1 秒,由 10 个波形组成,每个波形之间有 10ms 的延迟。
为了提供连续和同步的缓冲而没有噪声或间隙作为副作用,关键在于控制缓冲区的排队/流式传输。当 WAVE
链接到线程 INTERFACE
时,它必须与 WAI 接口或播放设备 (参见 HWAVEOUT
) 链接。通过调用 AddInterface
方法,将 INTERFACE
的 wfreeblock
计数器的指针作为参数传递,以便在回调 waveOutProc
中,当缓冲区从 WAI 队列播放或收到 WOM_DONE
消息时返回。通过在缓冲区发送到 WAI 队列播放时递减 wfreeblock
计数器,并在 waveOutProc
中缓冲区完成/播放时递增,可以轻松控制有限的 BLOCK_COUNT
个排队缓冲区的保持。如果任何一个 wfreeblock
缓冲区被释放,就会发送一个新的缓冲区到队列,因此音频流将跨所有 INTERFACE
s 连续且同步。您可以通过在其他 CPU 时间消耗进程 (例如 GUI 相关进程) 中同时播放 n 个 SUPPORT_WAVES
和 m 个 SUPPORT_INTERFACES
来将 PlayThread
推到极限。我相信这足以满足任何正确编码的任务,并能同时播放合理数量的波形/接口而没有间隙。
/// performance predefines #define SUPPORT_WAVES 10 // predefined wave count #define SUPPORT_INTERFACES 10 // predefined interface count /// buffering #define READ_BLOCK 8192 // read wave file block #define BLOCK_SIZE 8192 // queue block size #define BLOCK_COUNT 20 // queue block count #define BP_TURN 1 // blocks per turn /// AddInterface is wrapper method around WAI API waveOutOpen int CWaveBox::AddInterface( HWAVEOUT *dev, WAVEFORMATEX *wfx, volatile int *wfreeblock ) { // check for free device if( !waveOutGetNumDevs() ) return -1; // try to open the default wave device. WAVE_MAPPER is // a constant defined in mmsystem.h, it always points to the // default wave device on the system (some people have 2 or // more sound cards). if(waveOutOpen( dev, WAVE_MAPPER, wfx, (DWORD)waveOutProc, (DWORD)wfreeblock, CALLBACK_FUNCTION ) != MMSYSERR_NOERROR ) return -1; return 1; } /// waveOutProc is CALLBACK function /// and serve as a message queue of WAI where all /// devices HWAVEOUT ( threads ) notifies /// when any of action / message occurs static void CALLBACK waveOutProc( HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2 ) { // pointer to free block counter int* freeBlockCounter = (int*)dwInstance; // ignore calls that occur due to openining and closing the device. if(uMsg != WOM_DONE) return; // increase free block counter EnterCriticalSection(&cs); (*freeBlockCounter)++; LeaveCriticalSection(&cs); }
PlayThread
如果您跳过了“队列模型”段落,请阅读它,因为它将为您提供理解 PlayThread
如何与 WAI 驱动程序交互并播放多个波形的介绍。PlayThread
响应两种类型的消息。第一种是 Play
方法设置的波形 WMsg
消息,所以我们先讨论它们会触发什么。
当 WMSG_START
到达特定波形 wb->W[i];
时,线程继续搜索第一个 INT_FREE
接口以将波形链接进去。当找到空闲接口并通过 AddInterface
方法打开设备并分配 HWAVEOUT
句柄给当前接口后,WAVE
指针会被保存,以便接口可以工作。然后,接口被标记为 INT_USED
,所以稍后您会看到,线程将知道播放它以及当前连接的波形。wb->W[i].WMSG;
消息被设置回 WMSG_WAIT
状态,因此,如果需要,它可以再次在相同的或另一个 INTERFACE
上播放。这种链接模型使 CWaveBox
能够播放单个波形以及多个波形。
现在,它进入“主播放循环”范围,在那里,首先,对于每个 INT_USED
接口,都会检查 wb->I[k].wfreeblock;
计数器,以查看当前接口是否有可用于排队的空闲块。如果没有,循环继续搜索第一个有空闲块的。每次一个块,或 BP_TURN
常量,只是 BLOCK_SIZE
的乘数,用于每回合排队一个以上的块。您可以通过增大 BLOCK_SIZE
来避免这种情况。我真的不知道是每回合排队一个更大的块还是许多小块更好,所以这可以进一步测试。;-)
在“每回合一个块”循环中,首先,我们检查 Wave 数据头 WAVEHDR
块是否未准备好 (在以前排队时 (环形排队/缓冲)),并通过将 Wave data
块复制到 Wave 头成员 pData
来完成 WAVEHDR
块,并在 dwBufferLength
中设置块的大小。准备完成后,通过 waveOutWrite
WAI 方法将头发送到当前接口 wb->I[k].dev;
进行播放。剩余的工作是递减接口 wfreeblock
计数器,并对循环的 wcurrblock
计数器进行取整,使其指向下一个块。
如果最后一个块已排队,并且当前 INTERFACE
没有剩余的 data
可供排队,它将再次检查 wfreeblock
计数器,但这次它必须等于 BLOCK_COUNT
,这意味着所有缓冲区都已播放,因此所有准备好的缓冲区都可以成功地取消准备并调用 RemoveInterface
方法,该方法关闭当前 WAI 驱动程序线程并释放所有使用的内存。WAI 接口的关闭必须按此步骤进行,否则可能会失败并导致内存泄漏!剩余的工作是将接口 wb->I[k].state;
消息设置为 INT_FREE
,以便在需要时可以链接另一个 WAVE
。
另一种消息是线程 TMsg
消息。第一条消息 TMSG_ALIVE
由 Play
方法设置,并保持线程活动,直到 TMSG_CLOSE
由 CWaveBox
析构函数设置。当 TMSG_CLOSE
到达时,线程从“主线程”或 while
循环中中断,并继续执行 PlayThread
的底部。析构函数可能在 WAI 子系统中还有“未知数量的波形”仍在播放时调用,因此最后需要检查哪个接口的 state
标志设置为 INT_USED
,以便可以使用 waveOutReset
重置并通过 RemoveInterface
方法关闭。如果不释放 WAI 子系统,可能会出现问题 (主要是 Windows CE),在下一次启动 WAI 时,因此此过程是必需的。
最后要做的事情是返回 EXIT_THREAD
代码,这样它将向析构函数发出信号,使其继续销毁剩余的 CWaveBox
成员。;-)
static unsigned int __stdcall PlayThread( LPVOID lp )
{
/// get the class instance
CWaveBox *wb = ( CWaveBox *)lp;
/// pooling variables < most frequently used / checked >
register WMsg wmsg = WMSG_WAIT;
register TMsg tmsg = TMSG_ALIVE;
register unsigned int i = 0;
/// thread life cycle
while( tmsg )
{
/// check for 'play' msg
for( i = 0; i < wb->wload; i++ )
{
/// read msg
EnterCriticalSection( &cs );
wmsg = wb->W[i].WMSG;
LeaveCriticalSection( &cs );
/// wave to play?
if( wmsg == WMSG_START ) break;
}
/// playable wave
if( wmsg == WMSG_START )
/// link with first free interface
for( unsigned int j = 0; j < SUPPORT_INTERFACES; j++ )
/// check for free interface
if( wb->I[j].state == INT_FREE )
/// attach wave to interface
if( wb->AddInterface( &wb->I[j].dev,
&wb->W[i].wfx,
&wb->I[j].wfreeblock ) )
{
/// get wave pointer
wb->I[j].wave = &wb->W[i];
/// mark interface as used
wb->I[j].state = INT_USED;
/// free wave
EnterCriticalSection( &cs );
wb->W[i].WMSG = WMSG_WAIT;
LeaveCriticalSection( &cs );
/// leave loop
break;
}
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
///
/// < main playing loop >
///
/// search for the first marked interface and play attached wave
///
for( unsigned int k = 0; k < SUPPORT_INTERFACES; k++ )
{
/// nothing to do with free interface
if( wb->I[k].state == INT_FREE ) continue;
EnterCriticalSection( &cs );
int free = wb->I[k].wfreeblock;
LeaveCriticalSection( &cs );
/// nothing to do with full queued interface
if( free < BP_TURN ) continue;
WAVEHDR *current = NULL;
/// how much blocks per turn will be queued
for( unsigned int m = 0; m < BP_TURN; m++ )
{
/// set current block pointer
current = &wb->I[k].wblock[wb->I[k].wcurrblock];
// first make sure the header we're going to use is unprepared
if( current->dwFlags & WHDR_PREPARED )
waveOutUnprepareHeader( wb->I[k].dev,
current,
sizeof(WAVEHDR) );
/// how much data is left at this interface to play
unsigned long left = wb->I[k].wave->size - wb->I[k].wpos;
unsigned long chunk = 0;
if( left >= BLOCK_SIZE )
chunk = BLOCK_SIZE;
else
if( left && left < BLOCK_SIZE )
chunk = left;
else
{
////////////////////
/// nothing left ///
////////////////////////////////////////////////////////
///
/// < clean job, close waveOutProc threads >
///
/// all buffers are queued to the interface
///
/// get free block count
EnterCriticalSection( &cs );
int free = wb->I[k].wfreeblock;
LeaveCriticalSection( &cs );
if( free == BLOCK_COUNT ) /// are all blocks played!?
{
/// unprepare any blocks that are still prepared
for( int i = 0; i < wb->I[k].wfreeblock; i++)
if( wb->I[k].wblock[i].dwFlags & WHDR_PREPARED )
waveOutUnprepareHeader( wb->I[k].dev,
&wb->I[k].wblock[i],
sizeof(WAVEHDR));
/// close interface
if( wb->RemoveInterface( wb->I[k].dev ) )
{
/// free interface
wb->I[k].wcurrblock = 0;
wb->I[k].state = INT_FREE;
wb->I[k].wpos = 0;
wb->I[k].wave = NULL;
}
}
/// step out
break;
}
/// prepare current wave data block header
memcpy( current->lpData,
&wb->I[k].wave->data[wb->I[k].wpos], chunk );
current->dwBufferLength = chunk; // sizeof block
wb->I[k].wpos += chunk; // update position
/// prepare for playback
waveOutPrepareHeader( wb->I[k].dev,
current, sizeof(WAVEHDR) );
/// push to the queue
waveOutWrite(wb->I[k].dev, current, sizeof(WAVEHDR));
/// decrease free block counter
EnterCriticalSection( &cs );
wb->I[k].wfreeblock--;
LeaveCriticalSection( &cs );
/// point to the next block
wb->I[k].wcurrblock++;
wb->I[k].wcurrblock %= BLOCK_COUNT;
}/// block(s)
}/// interface(s)
/// wait 10 ms < save CPU time >
Sleep( 10 );
/// check for thread message
EnterCriticalSection( &cs );
tmsg = wb->TMSG;
LeaveCriticalSection( &cs );
}/// thread
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
///
/// force to close interfaces which are still playing
///
for( i = 0; i < SUPPORT_INTERFACES; i++ )
if( wb->I[i].state == INT_USED )
if( waveOutReset( wb->I[i].dev ) == MMSYSERR_NOERROR )
wb->RemoveInterface( wb->I[i].dev );
return THREAD_EXIT; /// return exit code < destructor >
}
~CWaveBox()
在析构函数中,对于已恢复的线程,会设置 TMSG_CLOSE
消息并循环,直到 THREAD_EXIT
代码到达或“线程软关闭”,这样所有正在播放的接口都会被强制重置和关闭。对于挂起的线程,会调用“线程硬关闭”或 TerminateThread
。之后,所有必需的缓冲区和关键代码段都会被释放。
CWaveBox::~CWaveBox() { unsigned long exit = 0; if( run ) // thread resumed { // set thread close message EnterCriticalSection( &cs ); TMSG = TMSG_CLOSE; LeaveCriticalSection( &cs ); do // wait for soft close { GetExitCodeThread( thread, &exit ); Sleep( 10 ); }while( exit != THREAD_EXIT ); }else // thread suspended { // hard close GetExitCodeThread( thread, &exit ); TerminateThread( thread, exit ); } // release wave(s) for( unsigned int i = 0; i < wload; i++ ) free( W[i].data ); // release interface(s) for( i = 0; i < SUPPORT_INTERFACES; i++ ) freeBlocks( I[i].wblock ); // del cs DeleteCriticalSection( &cs ); }
演示
此演示是一个简单有效的示例,展示了如何使用此类并播放带有时序的波形。您所要做的就是 Load
一个波形文件,选择/设置时序,然后 Play
它。打开您的音量,享受吧!;-)
注意:要在 Windows XP 上编译,请链接 'Winmm.lib' 库,而在 Windows CE 上,它已链接。
#include "stdafx.h" #include "wavebox.h" #include <stdio.h> #include <conio.h> int main(int argc, char* argv[]) { CWaveBox w; /// load waves w.Load("twilightzone.wav"); w.Load("badfeeling.wav"); w.Load("wolfcall.wav"); w.Load("heart.wav"); w.Load("gusting_winds.wav"); w.Load("newmail.wav"); w.Load("dumbass.wav"); w.Load("porky.wav"); w.Load("system_alert.wav"); printf("<CWaveBox v0.95 demo>:\n\n"); printf("You will listen 9 waves in this demo, approx. 30 sec.\n"); /// and play with them ;-) for( int f = 0; f < 5; f++ ){ w.Play(8); Sleep(50); } Sleep(2000); for( f = 0; f < 5; f++ ){ w.Play(8); Sleep(75);} for( int k = 0; k < 1000; k += 50 ) { w.Play(3); Sleep( 300 + 1000 - k ); if( !( k % 200 ) ) w.Play(2); if( k == 400 || k == 700 ) w.Play(1); if( k == 950 ) { w.Play(4); Sleep( 500 );} } w.Play(0);Sleep(2500); w.Play(5);Sleep(1500); w.Play(6);Sleep(4500); w.Play(7); printf("thats all folks! ;-) \n"); getch(); return 1; }
关注点
此类可以通过 UnLoad
方法进行扩展,因此任何波形都可以随时 Load
和 UnLoad
。此外,'即时'从文件播放,内存占用较低,但 CPU 占用较高,这可能是一个不错的扩展。然后,事件可以取代当前的轮询模型,并将 Play
扩展到支持压缩波形。我希望此类能连接到此处提供的 MPEG3“免版税”且“可免费商用”的解码器,这样我们就可以在我们的游戏和应用程序中自由地实现它。