65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (16投票s)

2005年4月15日

9分钟阅读

viewsIcon

172502

downloadIcon

4766

多波形播放器 ('波形音频接口' PCM 波形包装器)。

Sample Image

引言

您是否曾希望在 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, freeBlockswaveOutProc) 以及一些变量表示法。我就是个懒鬼。;-)

演示中混合的所有波形均从 此处 下载。

代码,简要概述

CWaveBox 类使用 WAVE 模型进行结构化,该模型用于存储由 Load 方法加载的波形 datasizewfx (格式信息) 以及由 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 方法,将 INTERFACEwfreeblock 计数器的指针作为参数传递,以便在回调 waveOutProc 中,当缓冲区从 WAI 队列播放或收到 WOM_DONE 消息时返回。通过在缓冲区发送到 WAI 队列播放时递减 wfreeblock 计数器,并在 waveOutProc 中缓冲区完成/播放时递增,可以轻松控制有限的 BLOCK_COUNT 个排队缓冲区的保持。如果任何一个 wfreeblock 缓冲区被释放,就会发送一个新的缓冲区到队列,因此音频流将跨所有 INTERFACEs 连续且同步。您可以通过在其他 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_ALIVEPlay 方法设置,并保持线程活动,直到 TMSG_CLOSECWaveBox 析构函数设置。当 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 方法进行扩展,因此任何波形都可以随时 LoadUnLoad。此外,'即时'从文件播放,内存占用较低,但 CPU 占用较高,这可能是一个不错的扩展。然后,事件可以取代当前的轮询模型,并将 Play 扩展到支持压缩波形。我希望此类能连接到此处提供的 MPEG3“免版税”且“可免费商用”的解码器,这样我们就可以在我们的游戏和应用程序中自由地实现它。

© . All rights reserved.