本篇是创建游戏内核(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();
}