本篇是创建游戏内核(17)的续篇,有关DirectAudio和DirectShow的基础知识请参阅用DirectX
Audio和DirectShow播放声音和音乐。
使用SOUND控制DirectX Audio对象
SOUND对象控制DirectSound和DirectMusic对象,控制回放声音时的音量(全局音量控制),也触发音频流相关消息。
来看看它的定义:
#define EVENT_NUM 32
//======================================================================================
// This class encapsulate for DirectPerformance and DirectLoader and sound play event handle.
//======================================================================================
class SOUND
{
protected:
/////////////////////////////// Sound system related ///////////////////////////////
HWND _hwnd; // pointer to parent window handle
long _volume; // global sound buffer volume
// Events for all sound channel, the last event takes charge to close all other events.
HANDLE _events[EVENT_NUM+1];
// all sound channel pointers
SOUND_CHANNEL* _sound_channel[EVENT_NUM];
HANDLE _thread_handle;
DWORD _thread_id;
BOOL _thread_active;
static DWORD handle_notifications(LPVOID data);
/////////////////////////////// Sound related ///////////////////////////////
IDirectSound8* _ds;
IDirectSoundBuffer* _ds_buffer;
long _coop_level;
long _frequency;
short _channels;
short _bits_per_sample;
/////////////////////////////// Music related - MIDI ///////////////////////////////
IDirectMusicPerformance8* _dm_perf;
IDirectMusicLoader8* _dm_loader;
public:
SOUND();
~SOUND();
// assign and release events
BOOL assign_event(SOUND_CHANNEL* sound_channel, short* event_index, HANDLE* event_handle);
BOOL release_event(SOUND_CHANNEL* sound_channel, short* event_index);
// function to retrieve com interfaces
IDirectSound8* get_directsound_com();
IDirectSoundBuffer* get_primary_buffer_com();
IDirectMusicPerformance8* get_performance_com();
IDirectMusicLoader8* get_loader_com();
// init and shutdown functions
BOOL init(HWND hwnd, long frequency = 22050, short channels = 1, short bits_per_sample = 16,
long coop_level = DSSCL_PRIORITY);
void shutdown();
// volume get/get
long get_volume();
BOOL set_volume(long percent);
// restore system to known state
void restore();
};
接着是它的实现:
//------------------------------------------------------------------------------
// Constructor, initialize member data.
//------------------------------------------------------------------------------
SOUND::SOUND()
{
// initialize com
CoInitialize(NULL);
memset(this, 0, sizeof(*this));
}
//------------------------------------------------------------------------------
// Destructor, release main sound buffer, close all event and thread.
//------------------------------------------------------------------------------
SOUND::~SOUND()
{
shutdown();
// uninitialize com
CoUninitialize();
}
//------------------------------------------------------------------------------
// Initialize DierctSound and DirectMusic.
//------------------------------------------------------------------------------
BOOL SOUND::init(HWND hwnd, long frequency, short channels, short bits_per_sample, long coop_level)
{
// shutdown system in case of prior install
shutdown();
// save parent window handle
if((_hwnd = hwnd) == NULL)
return FALSE;
///////////////////////////////////////////////////////////////////
// Initialize DirectSound
///////////////////////////////////////////////////////////////////
// save settings for sound setup
if(coop_level == DSSCL_NORMAL)
coop_level = DSSCL_PRIORITY;
_coop_level = coop_level;
_frequency = frequency;
_channels = channels;
_bits_per_sample = bits_per_sample;
// create an IDirectSound8 object
if(FAILED(DirectSoundCreate8(NULL, &_ds, NULL)))
return FALSE;
// set cooperative mode
if(FAILED(_ds->SetCooperativeLevel(_hwnd, _coop_level)))
return FALSE;
// create sound buffer
DSBUFFERDESC ds_buffer_desc;
// get primary buffer control
ZeroMemory(&ds_buffer_desc, sizeof(DSBUFFERDESC));
ds_buffer_desc.dwSize = sizeof(DSBUFFERDESC);
ds_buffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;
ds_buffer_desc.dwBufferBytes = 0;
ds_buffer_desc.lpwfxFormat = NULL;
if(FAILED(_ds->CreateSoundBuffer(&ds_buffer_desc, &_ds_buffer, NULL)))
return FALSE;
// set wave format for sound buffer
WAVEFORMATEX wave_format;
// set the primary buffer 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;
if(FAILED(_ds_buffer->SetFormat(&wave_format)))
return FALSE;
// create the events, plus an extra one for thread termination.
for(short i = 0; i < EVENT_NUM+1; i++)
{
if((_events[i] = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL)
return FALSE;
}
// create a thread for handling notifications
_thread_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) handle_notifications, this, 0, &_thread_id);
if(_thread_handle == NULL)
return FALSE;
///////////////////////////////////////////////////////////////////
// Initialize DirectMusic
///////////////////////////////////////////////////////////////////
// create the DirectMusic loader object
CoCreateInstance(CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**) &_dm_loader);
// create the DirectMusic performance object
CoCreateInstance(CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance8,
(void**) &_dm_perf);
// Initialize the performance with the standard audio path.
// This initializes both DirectMusic and DirectSound and sets up the synthesizer.
_dm_perf->InitAudio(NULL, NULL, _hwnd, DMUS_APATH_SHARED_STEREOPLUSREVERB, 128, DMUS_AUDIOF_ALL, NULL);
// set the performance global volume to +10 decibels
long volume_level = 1000;
if(FAILED(_dm_perf->SetGlobalParam(GUID_PerfMasterVolume, &volume_level, sizeof(long))))
return FALSE;
CHAR path[MAX_PATH];
WCHAR w_path[MAX_PATH];
// tell DirectMusic where the default search path is
GetCurrentDirectory(MAX_PATH, path);
MultiByteToWideChar(CP_ACP, 0, path, -1, w_path, MAX_PATH);
_dm_loader->SetSearchDirectory(GUID_DirectMusicAllTypes, w_path, FALSE);
// set default volume to full
set_volume(100);
return TRUE;
}
//------------------------------------------------------------------------------
// Release main sound buffer, close all events and threads.
//------------------------------------------------------------------------------
void SOUND::shutdown()
{
// stop the music, and close down.
if(_dm_perf != NULL)
{
_dm_perf->Stop(NULL, NULL, 0, 0);
_dm_perf->CloseDown();
}
// release the DirectMusic objects
release_com(_dm_perf);
release_com(_dm_loader);
// go through all used sound channels and free them
for(short i = 0; i < EVENT_NUM; i++)
{
if(_sound_channel[i] != NULL)
{
_sound_channel[i]->free();
_sound_channel[i] = NULL;
}
// clear the event status
if(_events[i] != NULL)
ResetEvent(_events[i]);
}
// stop the primary channel from playing
if(_ds_buffer != NULL)
_ds_buffer->Stop();
// release the DirectSound objects
release_com(_ds_buffer);
release_com(_ds);
// force a closure of the thread by triggering the last event and waiting for it to terminate
if(_thread_handle != NULL)
{
if(_events[EVENT_NUM] != NULL)
{
while(_thread_active)
// set the specified event object to the signaled state
SetEvent(_events[EVENT_NUM]);
}
else
{
// getting here means no event assigned
TerminateThread(_thread_handle, 0);
}
}
// close all event handles
for(short i = 0; i < EVENT_NUM+1; i++)
{
if(_events[i] != NULL)
{
CloseHandle(_events[i]);
_events[i] = NULL;
}
}
// free the thread handle
if(_thread_handle != NULL)
{
CloseHandle(_thread_handle);
_thread_handle = NULL;
}
_thread_id = 0;
}
//------------------------------------------------------------------------------
// Return pointer to DirectSound.
//------------------------------------------------------------------------------
IDirectSound8* SOUND::get_directsound_com()
{
return _ds;
}
//------------------------------------------------------------------------------
// Return pointer to primary DirectSound buffer.
//------------------------------------------------------------------------------
IDirectSoundBuffer* SOUND::get_primary_buffer_com()
{
return _ds_buffer;
}
//------------------------------------------------------------------------------
// Return pointer to DirectMusic performance object.
//------------------------------------------------------------------------------
IDirectMusicPerformance8* SOUND::get_performance_com()
{
return _dm_perf;
}
//------------------------------------------------------------------------------
// Return pointer to DirectMusic loader object.
//------------------------------------------------------------------------------
IDirectMusicLoader8* SOUND::get_loader_com()
{
return _dm_loader;
}
//------------------------------------------------------------------------------
// Assign sound channel with specified event.
//------------------------------------------------------------------------------
BOOL SOUND::assign_event(SOUND_CHANNEL* sound_channel, short* event_index, HANDLE* event_handle)
{
for(short i = 0; i < EVENT_NUM; i++)
{
if(_events[i] != NULL && _sound_channel[i] == NULL)
{
// set the specified event object to the nonsignaled state
ResetEvent(_events[i]);
_sound_channel[i] = sound_channel;
*event_index = i;
*event_handle = _events[i];
return TRUE;
}
}
return FALSE;
}
//------------------------------------------------------------------------------
// Set the event state to nonsignaled.
//------------------------------------------------------------------------------
BOOL SOUND::release_event(SOUND_CHANNEL* sound_channel, short* event_index)
{
if((unsigned short)(*event_index) < EVENT_NUM && _sound_channel[*event_index] == sound_channel)
{
ResetEvent(_events[*event_index]);
// set event channel pointer with NULL
_sound_channel[*event_index] = NULL;
*event_index = -1;
return TRUE;
}
return FALSE;
}
//------------------------------------------------------------------------------
// Get global sound volume.
//------------------------------------------------------------------------------
long SOUND::get_volume()
{
return _volume;
}
//------------------------------------------------------------------------------
// Set the global sound volume.
//------------------------------------------------------------------------------
BOOL SOUND::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;
}
//------------------------------------------------------------------------------
// Handle all sound events.
//------------------------------------------------------------------------------
DWORD SOUND::handle_notifications(LPVOID data)
{
MSG msg;
SOUND* sound = (SOUND*) data;
sound->_thread_active = TRUE;
BOOL complete = FALSE;
while(! complete)
{
// wait for a message
DWORD result = MsgWaitForMultipleObjects(EVENT_NUM+1, sound->_events, FALSE, INFINITE, QS_ALLEVENTS);
// get channel index to update
DWORD channel_index = result - WAIT_OBJECT_0;
// check for channel update
if(channel_index >= 0 && channel_index < EVENT_NUM)
{
if(sound->_sound_channel[channel_index] != NULL)
sound->_sound_channel[channel_index]->_update();
}
else if(channel_index == EVENT_NUM) // check for thread closure
complete = TRUE;
else if(channel_index > EVENT_NUM) // check for waiting messages
{
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message == WM_QUIT)
{
complete = TRUE;
break;
}
}
}
}
sound->_thread_active = FALSE;
return 0L;
}
//------------------------------------------------------------------------------
// Restore primary DirectSound buffer and DirectSound channel buffer.
//------------------------------------------------------------------------------
void SOUND::restore()
{
// handle primary
if(_ds_buffer != NULL)
_ds_buffer->Restore();
// handle all used sound channels
for(short i = 0; i < EVENT_NUM; i++)
{
if(_sound_channel[i] != NULL)
_sound_channel[i]->_ds_buffer->Restore();
}
}
在SOUND类中,使用的函数主要有init,shutdown,set_volume。要使用init,必须给它传递一个父窗口的句柄,以及可选的混音器(mixer)设置(系统默认为22050赫兹,单声道,使用DSSCL_PRIORITY协作级别的16位采样)。
SOUND::set_volume将percent参数设置为0(静音)-- 100(最大音量)之间的值,就可以改变音量的设置。