天行健 君子当自强而不息

创建游戏内核(19)

 

本篇是创建游戏内核(18)的续篇,有关DirectAudio和DirectShow的基础知识请参阅用DirectX Audio和DirectShow播放声音和音乐

 

使用SOUND_CHANNEL播放声音

前面介绍了如何初始化声音系统以及如何加载声音数据,很自然地,接下来就要讲述如何播放声音了,这也正是SOUND_CHANNEL类的用途所在。

首先定义两个全局变量来保存音频缓冲的大小和每个音频数据块的大小。

// these are the fixed sizes for sound channel buffers
const long g_sound_buffer_size = 65536;
const long g_sound_buffer_chunk = g_sound_buffer_size / 4;

第一个变量g_sound_buffer_size表示分配给用于播放声音的每个DirectSound缓冲区的字节数,这里使用65536字节,因为此大小的缓冲区足够保存相当于数秒高品质的声音数据。第二个g_sound_buffer_chunk是单个声音数据块的大小,使用4个数据块,每个数据块存储了流式声音的一个小的采样。每播放完一个数据块,紧接着就 开始播放下一个数据块,同时使用新的声音数据加载前一个数据块。

一般不必改变这两个变量的值,除非想要节约内存。如果想节约内存,只需将g_sound_buffer_size变量的值改变成较小的数字即可。g_sound_buffer_size变量使用不同的大小,各有其优势,比如g_sound_buffer_size变量的值越大,声音内核将新数据放到流中的频率就越小,当然这也意味着要占用更多的内存。

来看看SOUND_CHANNEL的定义:

//======================================================================================
// This class encapsulate sound buffer playing.
//======================================================================================
class SOUND_CHANNEL
{
private:
    friend 
class SOUND;

protected:
    SOUND*                  _sound;         
// pointer to parent sound object
    IDirectSoundBuffer8*    _ds_buffer;     // pointer to DirectSound buffer object
    IDirectSoundNotify8*    _ds_notify;     // pointer to DirectSound notify object
    short                   _event_index;   

    
long                    _volume;        // sound buffer volume
    signed long             _pan;           // sound buffer pan
    BOOL                    _is_playing;    // sound buffer playing flag
    long                    _loop_times;    // loop times

    
long                    _frequency;
    
short                   _bits_per_sample;
    
short                   _channels;

    SOUND_DATA              _sound_data;

    
short                   _load_section;  // sound section will to be loaded
    short                   _stop_section;  // sound section will to be stoped
    short                   _next_notify;   // sound notification index will to be played

    BOOL _buffer_data();
    
void _update();

public:
    SOUND_CHANNEL();
    ~SOUND_CHANNEL();

    IDirectSoundBuffer8*    get_sound_buffer_com();
    IDirectSoundNotify8*    get_notify_com();

    BOOL create(SOUND* sound, 
long frequency = 22050, short channels = 1, short bits_per_sample = 16);
    BOOL create(SOUND* sound, SOUND_DATA* sound_data);
    
void free();

    BOOL play(SOUND_DATA* sound_data, 
long volume_percent = 100, long loop = 1);
    
void stop();

    
long get_volume();
    BOOL set_volume(
long percent);

    signed 
long get_pan();
    BOOL set_pan(signed 
long level);

    
long get_frequency();
    BOOL set_frequency(
long frequency);

    BOOL is_playing();
};
 

接着来看看它的实现:

//------------------------------------------------------------------------------
// Constructor, initialize member data.
//------------------------------------------------------------------------------
SOUND_CHANNEL::SOUND_CHANNEL()
{
    _sound     = NULL;
    _ds_buffer = NULL;
    _ds_notify = NULL;

    _event_index = -1;

    _volume     = 0;
    _pan        = 0;
    _frequency  = 0;
    _is_playing = FALSE;
}

//------------------------------------------------------------------------------
// Destructor, release sound buffer and sound notification, set the event state 
// to nonsignaled.
//------------------------------------------------------------------------------
SOUND_CHANNEL::~SOUND_CHANNEL()
{
    free();
}

//------------------------------------------------------------------------------
// Return pointer to DirectSound buffer.
//------------------------------------------------------------------------------
IDirectSoundBuffer8* SOUND_CHANNEL::get_sound_buffer_com()
{
    
return _ds_buffer;
}

//------------------------------------------------------------------------------
// Return pointer to DirectSound notify.
//------------------------------------------------------------------------------
IDirectSoundNotify8* SOUND_CHANNEL::get_notify_com()
{
    
return _ds_notify;
}

//------------------------------------------------------------------------------
// Create sound buffer, set sound notification and event.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::create(SOUND* sound, long frequency, short channels, short bits_per_sample)
{
    
// free a prior channel
    free();

    
if((_sound = sound) == NULL)
        
return FALSE;

    
if(_sound->get_directsound_com() == NULL)
        
return FALSE;

    
// save playback format
    _frequency       = frequency;
    _bits_per_sample = bits_per_sample;
    _channels        = channels;

    WAVEFORMATEX wave_format;

    
// create a new sound buffer for this channel, using specified format.
    ZeroMemory(&wave_format, sizeof(WAVEFORMATEX));

    wave_format.wFormatTag      = WAVE_FORMAT_PCM;
    wave_format.nChannels       = (WORD) _channels;
    wave_format.nSamplesPerSec  = _frequency;
    wave_format.wBitsPerSample  = (WORD) _bits_per_sample;
    wave_format.nBlockAlign     = wave_format.wBitsPerSample / 8 * wave_format.nChannels;
    wave_format.nAvgBytesPerSec = wave_format.nSamplesPerSec * wave_format.nBlockAlign;

    DSBUFFERDESC buffer_desc;

    ZeroMemory(&buffer_desc, 
sizeof(DSBUFFERDESC));

    buffer_desc.dwSize          = 
sizeof(DSBUFFERDESC);
    buffer_desc.dwFlags         = DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPAN | DSBCAPS_CTRLFREQUENCY | 
                                  DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_LOCSOFTWARE;
    buffer_desc.dwBufferBytes   = g_sound_buffer_size;
    buffer_desc.lpwfxFormat     = &wave_format;

    IDirectSoundBuffer* ds_buffer;

    
if(FAILED(_sound->get_directsound_com()->CreateSoundBuffer(&buffer_desc, &ds_buffer, NULL)))
        
return FALSE;

    
// query for newer interface
    if(FAILED(ds_buffer->QueryInterface(IID_IDirectSoundBuffer8, (void**) &_ds_buffer)))
    {
        ds_buffer->Release();
        
return FALSE;
    }

    
// release old object - we have the newer one now
    ds_buffer->Release();

    
// create the notification interface
    if(FAILED(_ds_buffer->QueryInterface(IID_IDirectSoundNotify8, (void**) &_ds_notify)))
        
return FALSE;

    HANDLE event_handle;

    
// get an event for this
    if(! _sound->assign_event(this, &_event_index, &event_handle))
        
return FALSE;

    DSBPOSITIONNOTIFY pos_notify[4];

    
// setup the 4 notification positions
    pos_notify[0].dwOffset     = g_sound_buffer_chunk - 1;
    pos_notify[0].hEventNotify = event_handle;
    pos_notify[1].dwOffset     = g_sound_buffer_chunk * 2 - 1;
    pos_notify[1].hEventNotify = event_handle;
    pos_notify[2].dwOffset     = g_sound_buffer_chunk * 3 - 1;
    pos_notify[2].hEventNotify = event_handle;
    pos_notify[3].dwOffset     = g_sound_buffer_size - 1;
    pos_notify[3].hEventNotify = event_handle;

    
if(FAILED(_ds_notify->SetNotificationPositions(4, pos_notify)))
        
return FALSE;

    
// set the pan and default volume
    set_volume(100);
    set_pan(0);

    
return TRUE;
}

//------------------------------------------------------------------------------
// Create sound buffer.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::create(SOUND* sound, SOUND_DATA* sound_data)
{
    
return create(sound, sound_data->_frequency, sound_data->_channels, sound_data->_bits_per_sample);
}

//------------------------------------------------------------------------------
// Release sound buffer and sound notification, set the event state to nonsignaled.
//------------------------------------------------------------------------------
void SOUND_CHANNEL::free()
{
    
// stop any playback
    stop();

    
// release the notification
    release_com(_ds_notify);

    
// release the sound buffer
    release_com(_ds_buffer);

    
// release event from parent SOUND class
    _sound->release_event(this, &_event_index);

    
// set to no parent sound
    _sound = NULL;
}

//------------------------------------------------------------------------------
// Play sound buffer.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::play(SOUND_DATA* sound_data, long volume_percent, long loop_times)
{
    
if(sound_data == NULL || _ds_buffer == NULL || _ds_notify == NULL)
        
return FALSE;

    
// stop any playback
    stop();

    
// restore a lost buffer just in case
    _ds_buffer->Restore();

    
// setup playing information
    _sound_data.copy(sound_data);

    
// set looping times
    _loop_times = loop_times;

    
// calculate stop section position
    if(_loop_times == 0)
        _stop_section = -1;
    
else
        _stop_section = (
short
            (((_sound_data._buffer_size * _loop_times) % g_sound_buffer_size) / g_sound_buffer_chunk) ;

    _load_section = 0;

    
// load sound data into sound buffer from sound file or sound data object
    _buffer_data();
    _buffer_data();
    _buffer_data();
    _buffer_data();

    
// set the volume
    set_volume(volume_percent);

    
// set position and begin play

    _next_notify = 0;

    
if(FAILED(_ds_buffer->SetCurrentPosition(0)))
        
return FALSE;

    
if(FAILED(_ds_buffer->Play(0, 0, DSBPLAY_LOOPING)))
        
return FALSE;

    
// flag as playing
    _is_playing = TRUE;

    
return TRUE;
}

//------------------------------------------------------------------------------
// Stop playing DirectSound buffer.
//------------------------------------------------------------------------------
void SOUND_CHANNEL::stop()
{
    
if(_ds_buffer)
        _ds_buffer->Stop();

    _is_playing = FALSE;
}

//------------------------------------------------------------------------------
// Get sound buffer volume.
//------------------------------------------------------------------------------
long SOUND_CHANNEL::get_volume()
{
    
return _volume;
}

//------------------------------------------------------------------------------
// Set volume for sound buffer.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::set_volume(long percent)
{
    
long volume;

    
if(_ds_buffer == NULL)
        
return FALSE;

    
// calculate a usable volume level
    if(percent == 0)
        volume = DSBVOLUME_MIN;
    
else
        volume = -20 * (100 - (percent % 101));

    
if(FAILED(_ds_buffer->SetVolume(volume)))
        
return FALSE;

    _volume = percent % 101;

    
return TRUE;
}

//------------------------------------------------------------------------------
// Get sound buffer pan.
//------------------------------------------------------------------------------
signed long SOUND_CHANNEL::get_pan()
{
    
return _pan;
}

//------------------------------------------------------------------------------
// Set pan for sound buffer.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::set_pan(long level)
{
    signed 
long pan;
    
    
if(_ds_buffer == NULL)
        
return FALSE;

    
// calculate a suable setting
    if(level < 0)
        pan = DSBPAN_LEFT / 100 * (-level % 101);
    
else
        pan = DSBPAN_LEFT / 100 * (level % 101);

    
if(FAILED(_ds_buffer->SetPan(pan)))
        
return FALSE;

    _pan = level % 101;

    
return TRUE;
}

//------------------------------------------------------------------------------
// Get sound buffer frequency.
//------------------------------------------------------------------------------
long SOUND_CHANNEL::get_frequency()
{
    
return _frequency;
}

//------------------------------------------------------------------------------
// Set frequency for sound buffer.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::set_frequency(long frequency)
{
    
if(_ds_buffer == NULL)
        
return FALSE;

    
if(FAILED(_ds_buffer->SetFrequency(frequency)))
        
return FALSE;

    _frequency = frequency;

    
return TRUE;
}

//------------------------------------------------------------------------------
// Checks whether sound buffer is playing.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::is_playing()
{
    
if(_sound == NULL || _ds_buffer == NULL || _ds_notify == NULL)
        
return FALSE;

    
return _is_playing;
}

//------------------------------------------------------------------------------
// Load sound data into sound buffer from sound file or sound data object.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::_buffer_data()
{
    
if(_ds_buffer == NULL)
        
return FALSE;

    
// setup position to load in
    long lock_pos = (_load_section % 4) * g_sound_buffer_chunk;

    
long size;
    
char* ptr;

    
// lock sound buffer to get pointer to sound data
    if(FAILED(_ds_buffer->Lock(lock_pos, g_sound_buffer_chunk, (void**) &ptr, (DWORD*)&size, NULL, NULL, 0)))
        
return FALSE;

    
// clear out buffer if nothing left to load
    if(_sound_data._left_size == 0)
        ZeroMemory(ptr, size);
    
else
    {
        
// load in the data - take looping into account
        long load_size = size;
        
long load_pos  = 0;

        
// load sound data until specfied load size is satisfied
        for(;;)
        {
            
if(_sound_data._left_size > load_size)
            {
                
// load in sound data
                if(_sound_data._fp != NULL)
                {
                    
// load in sound data from file
                    fseek(_sound_data._fp, _sound_data._file_curr_pos, SEEK_SET);
                    fread(&ptr[load_pos], 1, load_size, _sound_data._fp);
                }
                
else
                    
// load into sound data from buffer
                    memcpy(&ptr[load_pos], &_sound_data._ptr[_sound_data._file_curr_pos], load_size);

                
// decrease sound data, advance current sound buffer position.
                _sound_data._left_size     -= load_size;
                _sound_data._file_curr_pos += load_size;
                
break;
            }
            
else        // _sound_data._left_size <= load_size
            {
                
// load in sound data
                if(_sound_data._fp != NULL)
                {
                    
// load in sound data from file
                    fseek(_sound_data._fp, _sound_data._file_curr_pos, SEEK_SET);
                    fread(&ptr[load_pos], 1, _sound_data._left_size, _sound_data._fp);
                }
                
else
                    
// load in sound data from buffer
                    memcpy(&ptr[load_pos], &_sound_data._ptr[_sound_data._file_curr_pos], _sound_data._left_size);

                
// decrease sound data, advance current sound buffer position.
                load_size -= _sound_data._left_size;
                load_pos  += _sound_data._left_size;

                
// check if we need to stop loop
                if(_loop_times >= 1)
                {
                    _loop_times--;

                    
if(_loop_times == 0)
                    {
                        
// clear out remaining buffer space
                        if(load_size)
                            ZeroMemory(&ptr[load_pos], load_size);

                        _sound_data._left_size = 0L;
                        
break;
                    }
                }

                
// reset sound data current position and left size
                _sound_data._file_curr_pos = _sound_data._file_start_pos;
                _sound_data._left_size     = _sound_data._buffer_size;

                
// set if we need to stop loading data
                if(load_size == 0)
                    
break;
            }
        }
    }

    
// unlock the buffer
    _ds_buffer->Unlock(ptr, size, NULL, 0);

    
// mark next section to load
    if(++_load_section > 3)
        _load_section = 0;

    
return TRUE;
}

//------------------------------------------------------------------------------
// Update for sound buffer palying.
//------------------------------------------------------------------------------
void SOUND_CHANNEL::_update()
{
    
// check for end of sound
    if(_next_notify == _stop_section && _sound_data._left_size == 0)
        stop();
    
else
    {
        
// buffer in more data
        _buffer_data();

        
if(++_next_notify > 3)
            _next_notify = 0;
    }
}
 

最多可以将SOUND_CHANNEL类实例化32次,也就是说同时可以用多达32个声道进行播放(声音内核不允许同时32个以上的实例,任何超过32的值,都无法被成功初始化)。调用SOUND_CHANNEL::create函数可以初始化各个声道,需要提供一个预初始化的SOUND类以及回放格式。为方便起见,甚至可以使用存储在SOUND_DATA类中的回放格式创建声道。

使用SOUND_CHANNEL类进行的操作中,最频繁的就是播放声音、停止播放声音以及改变声音的音量。要播放声音,需要给SOUND_CHANNEL::play函数传递三个参数:保存在SOUND_DATA类中要播放的声音数据、音量的大小以及连续播放声音的次数。

接着我们编写测试代码:

点击下载源码和工程

/*****************************************************************************
PURPOSE:
    Test for class SOUND, SOUND_DATA, SOUND_CHANNEL.
*****************************************************************************/


#include "Core_Global.h"

class APP : public APPLICATION
{
private:
    SOUND _sound;
    SOUND_DATA _sound_data[2];
    SOUND_CHANNEL _sound_channel[2];

    FILE* _fp;

public:
    APP()
    {
        _fp = NULL;
    }

    BOOL init();
    BOOL frame();
    BOOL shutdown();
};

BOOL APP::init()
{
    
// Initialize DierctSound and DirectMusic.
    _sound.init(get_hwnd());

    
// load into sound data from wave file
    _sound_data[0].load_wav("test1.wav", NULL);
    _sound_data[1].load_wav("test2.wav", NULL);
    
    
// create sound channel
    _sound_channel[0].create(&_sound, &_sound_data[0]);
    _sound_channel[1].create(&_sound, &_sound_data[1]);

    
// play sound
    _sound_channel[0].play(&_sound_data[0]);
    _sound_channel[1].play(&_sound_data[1], 100, 0); 
// lopping forever   
    
    
return TRUE;
}

BOOL APP::frame()
{    
    
return TRUE;
}

BOOL APP::shutdown()
{
    
if(_fp) 
        fclose(_fp);

    
return TRUE;
}

int PASCAL WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
    APP app;

    
return app.run();
}

posted on 2007-09-29 22:03 lovedday 阅读(350) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


公告

导航

统计

常用链接

随笔分类(178)

3D游戏编程相关链接

搜索

最新评论