創(chuàng)建游戲內(nèi)核(17)
有關(guān)DirectAudio和DirectShow的基礎(chǔ)知識請參閱用DirectX Audio和DirectShow播放聲音和音樂。
聲音內(nèi)核為快速和容易地將聲音和音樂加入到游戲中提供了一種解決方案,聲音內(nèi)核包含6個類組件,如下表所示:
類 | 說明 |
SOUND | 包含DirectSound和DirectMusic對象,并控制音頻流(sound streaming)。 |
SOUND_DATA | 這個類包含了使用SOUND_CHANNEL播放的波形數(shù)據(jù)。 |
SOUND_CHANNEL | 這個類用于播放單個聲音,這種類一次最多可以同時使用32個(也就是說可以播放32個同步的聲音)。 |
MUSIC_CHANNEL | 可以使用這個類播放單個歌曲文件,無論此文件是MIDI文件還是DirectMusic本地歌曲,一次只能使用一個這樣的類。 |
DLS | 可下載聲音(downloadable sound)類對象,這個類允許用戶將不同的樂器加載到MUSIC_CHANNEL對象。 |
MP3 | 一個.mp3音樂播放類對象,這個類允許用戶播放.mp3歌曲并檢測這些歌曲的當(dāng)前播放狀態(tài)。 |
波形數(shù)據(jù)和SOUND_DATA
SOUND_DATA類對象用于描述和包含單個的聲音(波形)。聲音頻率(sound frequency)、采樣精度(bits-per-sample)、音頻聲道數(shù)(number of channels)、文件大小以及聲源(source)都包含在SOUND_DATA類的聲明中,來看看它的定義:
// This class encapsulate how to load sound data.
//======================================================================================
class SOUND_DATA
{
private:
friend class SOUND_CHANNEL; // let SOUND_CHANNEL can use this class's member data and function
protected:
long _frequency;
short _channels;
short _bits_per_sample;
FILE* _fp; // pointer to sound file
char* _ptr; // pointer to current sound buffer will to be played
char* _buf; // buffer to store sound data
long _buffer_size; // sound buffer size
long _left_size; // left size of sound buffer which need to loaded
long _file_start_pos; // start position of wave file will to be loaded
long _file_curr_pos; // current position of wave file will to be loaded
public:
SOUND_DATA();
~SOUND_DATA();
char* get_ptr();
long get_size();
BOOL create();
BOOL create(long size);
void free();
void set_format(long frequency, short channels, short bits_per_sample);
void set_source(FILE* fp, long pos = -1, long size = -1);
void set_source(void* ptr, long pos = -1, long size = -1);
BOOL load_wav(char* filename, FILE* fp = NULL);
BOOL load_wav_header(char* filename, FILE* fp = NULL);
BOOL copy(SOUND_DATA* source);
};
#pragma pack(1)
struct WAVE_HEADER
{
char riff_sig[4]; // 'RIFF'
long waveform_chunk_size; // 8
char wave_sig[4]; // 'WAVE'
char format_sig[4]; // 'fmt ' (notice space after)
long format_chunk_size; // 16;
short format_tag; // WAVE_FORMAT_PCM
short channels; // # of channels
long sample_rate; // sampling rate
long bytes_per_sec; // bytes per second
short block_align; // sample block alignment
short bits_per_sample; // bits per second
char data_sig[4]; // 'data'
long data_size; // size of waveform data
};
#pragma pack()
接著來看看SOUND_DATA的實現(xiàn):
// Constructor, initialize member data.
//------------------------------------------------------------------------------
SOUND_DATA::SOUND_DATA()
{
memset(this, 0, sizeof(*this));
_frequency = 22050;
_channels = 1;
_bits_per_sample = 16;
}
//------------------------------------------------------------------------------
// Destructor, release sound data buffer.
//------------------------------------------------------------------------------
SOUND_DATA::~SOUND_DATA()
{
free();
}
//------------------------------------------------------------------------------
// Create sound data buffer.
//------------------------------------------------------------------------------
BOOL SOUND_DATA::create()
{
return create(_buffer_size);
}
//------------------------------------------------------------------------------
// Create sound data buffer with specified size.
//------------------------------------------------------------------------------
BOOL SOUND_DATA::create(long size)
{
// free prior allocated data
free();
// check for valid size
if((_buffer_size = size) == 0)
return FALSE;
// create a new buffer
_buf = new char[_buffer_size];
if(_buf == NULL)
return FALSE;
// clear out new buffer
ZeroMemory(_buf, _buffer_size);
// point to new buffer
_ptr = _buf;
_fp = NULL;
return TRUE;
}
//------------------------------------------------------------------------------
// Release sound data buffer.
//------------------------------------------------------------------------------
void SOUND_DATA::free()
{
if(_buf != NULL)
{
delete[] _buf;
_buf = NULL;
}
_ptr = NULL;
_buffer_size = 0;
}
//------------------------------------------------------------------------------
// Get pointer to sound data buffer.
//------------------------------------------------------------------------------
char* SOUND_DATA::get_ptr()
{
return _buf;
}
//------------------------------------------------------------------------------
// Get size of sound data buffer.
//------------------------------------------------------------------------------
long SOUND_DATA::get_size()
{
return _buffer_size;
}
//------------------------------------------------------------------------------
// Set play property for sound data buffer.
//------------------------------------------------------------------------------
void SOUND_DATA::set_format(long frequency, short channels, short bits_per_sample)
{
_frequency = frequency;
_channels = channels;
_bits_per_sample = bits_per_sample;
}
//------------------------------------------------------------------------------
// Set position and size for sound data buffer.
//------------------------------------------------------------------------------
void SOUND_DATA::set_source(FILE* fp, long pos, long size)
{
_fp = fp;
_ptr = NULL;
if(pos != -1)
_file_start_pos = _file_curr_pos = pos;
if(size != -1)
_buffer_size = _left_size = size;
}
//------------------------------------------------------------------------------
// Set position and size for sound buffer.
//------------------------------------------------------------------------------
void SOUND_DATA::set_source(void* ptr, long pos, long size)
{
_fp = NULL;
_ptr = (char*) ptr;
if(pos != -1)
_file_start_pos = _file_curr_pos = pos;
if(size != -1)
_buffer_size = _left_size = size;
}
//------------------------------------------------------------------------------
// Load in wave file.
//------------------------------------------------------------------------------
BOOL SOUND_DATA::load_wav(char* filename, FILE* fp)
{
// load wave header information first
if(! load_wav_header(filename, fp))
return FALSE;
// create sound data buffer.
if(! create())
return FALSE;
// open file, seek to position and read in data.
if(filename != NULL)
{
if((fp = fopen(filename, "rb")) == NULL)
return FALSE;
}
fseek(fp, _file_start_pos, SEEK_SET);
fread(_buf, 1, _buffer_size, fp);
// reset start position and current position
_file_start_pos = _file_curr_pos = 0;
// close file
if(filename != NULL)
fclose(fp);
return TRUE;
}
//------------------------------------------------------------------------------
// Load wave header from file.
//------------------------------------------------------------------------------
BOOL SOUND_DATA::load_wav_header(char* filename, FILE* fp)
{
if(filename == NULL && fp == NULL)
return FALSE;
if(filename != NULL)
{
if((fp = fopen(filename, "rb")) == NULL)
return FALSE;
}
// save position in file
long org_file_pos = ftell(fp);
BOOL rv = FALSE;
// read in header and parse
WAVE_HEADER wave_header;
fread(&wave_header, 1, sizeof(WAVE_HEADER), fp);
// check signature
if(!memcmp(wave_header.riff_sig, "RIFF", 4) && !memcmp(wave_header.wave_sig, "WAVE", 4) &&
!memcmp(wave_header.format_sig, "fmt ", 4) && !memcmp(wave_header.data_sig, "data", 4))
{
// all signatures satisfied
_frequency = wave_header.sample_rate;
_channels = wave_header.channels;
_bits_per_sample = wave_header.bits_per_sample;
_buffer_size = _left_size = wave_header.data_size;
_file_start_pos = _file_curr_pos = ftell(fp);
rv = TRUE;
}
// close if we opened file, otherwise return to original position.
if(filename != NULL)
fclose(fp);
else
fseek(fp, org_file_pos, SEEK_SET);
return rv;
}
//------------------------------------------------------------------------------
// Copy sound data from another sound data.
//------------------------------------------------------------------------------
BOOL SOUND_DATA::copy(SOUND_DATA* source)
{
if(source == NULL)
return FALSE;
// note that _buf is not assigned!!
_frequency = source->_frequency;
_channels = source->_channels;
_bits_per_sample = source->_bits_per_sample;
_fp = source->_fp;
_ptr = source->_ptr;
_buffer_size = source->_buffer_size;
_left_size = source->_left_size;
_file_curr_pos = source->_file_curr_pos;
_file_start_pos = source->_file_start_pos;
return TRUE;
}
使用SOUND_DATA類對象存儲回放格式和聲音的數(shù)據(jù)源,聲音有兩個來源:一個文件或內(nèi)存緩沖區(qū)。另外,如果聲音太大而不能放進內(nèi)存中,可以設(shè)置成從音頻流讀取。
加載單個.wav文件,最快的方法就是使用SOUND_DATA::load_wav函數(shù)。load_wav函數(shù)帶有兩個參數(shù):要加載的.wav文件的文件名以及源文件指針。對于這兩個參數(shù),只能用其中一個,同時將另外一個設(shè)置為NULL。源文件指針使程序員能夠?qū)⒍鄠€.wav文件打包進單個文件,并且仍然能夠單獨加載這些被打包的.wav文件。
除了加載單個的.wav文件外,還可以采用設(shè)置聲音的數(shù)據(jù)源這種方式。當(dāng)聲音文件太大(超過64k)而無法存放到聲音緩沖區(qū)中的時候,這種方式就特別有用,方法就是將文件或內(nèi)存緩沖區(qū)中的聲音數(shù)據(jù)放到流中。SOUND_DATA::set_source函數(shù)正好用于解決這個問題,此函數(shù)有兩個版本可供使用:
void set_source(FILE* fp, long pos = -1, long size = -1);
void set_source(void* ptr, long pos = -1, long size = -1);
可以選擇一個源文件指針或一個內(nèi)存指針,pos參數(shù)將聲音數(shù)據(jù)的起始位置(偏移量)傳送給SOUND_DATA類,size參數(shù)用于設(shè)置流的總字節(jié)數(shù)(聲音的大小)。
注意pos和size的缺省值都是-1,就使得類能夠?qū)ξ募恢眠M行設(shè)置。為了達到設(shè)置聲音數(shù)據(jù)源的目的,首先必須使用set_format函數(shù)設(shè)置回放格式,然后必須使用load_wav_header函數(shù)解析波形文件頭,此函數(shù)所帶參數(shù)的含義同load_wav函數(shù)相同。
另外,如果聲音被存儲到內(nèi)存中以及從內(nèi)存中流出,就必須使用SOUND_DATA::create函數(shù)創(chuàng)建此內(nèi)存緩沖區(qū)??梢宰约褐付ň彌_區(qū)的大小,也可以讓create函數(shù)使用通過load_wav_header函數(shù)解析出來的緩沖區(qū)大小。調(diào)用SOUND_DATA::get_ptr函數(shù)可以得到指向內(nèi)存緩沖區(qū)的指針,可以安全地使用此指針存儲聲音。