创建游戏内核(17)
有关DirectAudio和DirectShow的基础知识请参阅用DirectX Audio和DirectShow播放声音和音乐。
声音内核为快速和容易地将声音和音乐加入到游戏中提供了一种解决方案,声音内核包含6个类组件,如下表所示:
类 | 说明 |
SOUND | 包含DirectSound和DirectMusic对象,并控制音频流(sound streaming)。 |
SOUND_DATA | 这个类包含了使用SOUND_CHANNEL播放的波形数据。 |
SOUND_CHANNEL | 这个类用于播放单个声音,这种类一次最多可以同时使用32个(也就是说可以播放32个同步的声音)。 |
MUSIC_CHANNEL | 可以使用这个类播放单个歌曲文件,无论此文件是MIDI文件还是DirectMusic本地歌曲,一次只能使用一个这样的类。 |
DLS | 可下载声音(downloadable sound)类对象,这个类允许用户将不同的乐器加载到MUSIC_CHANNEL对象。 |
MP3 | 一个.mp3音乐播放类对象,这个类允许用户播放.mp3歌曲并检测这些歌曲的当前播放状态。 |
波形数据和SOUND_DATA
SOUND_DATA类对象用于描述和包含单个的声音(波形)。声音频率(sound frequency)、采样精度(bits-per-sample)、音频声道数(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的实现:
// 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类对象存储回放格式和声音的数据源,声音有两个来源:一个文件或内存缓冲区。另外,如果声音太大而不能放进内存中,可以设置成从音频流读取。
加载单个.wav文件,最快的方法就是使用SOUND_DATA::load_wav函数。load_wav函数带有两个参数:要加载的.wav文件的文件名以及源文件指针。对于这两个参数,只能用其中一个,同时将另外一个设置为NULL。源文件指针使程序员能够将多个.wav文件打包进单个文件,并且仍然能够单独加载这些被打包的.wav文件。
除了加载单个的.wav文件外,还可以采用设置声音的数据源这种方式。当声音文件太大(超过64k)而无法存放到声音缓冲区中的时候,这种方式就特别有用,方法就是将文件或内存缓冲区中的声音数据放到流中。SOUND_DATA::set_source函数正好用于解决这个问题,此函数有两个版本可供使用:
void set_source(FILE* fp, long pos = -1, long size = -1);
void set_source(void* ptr, long pos = -1, long size = -1);
可以选择一个源文件指针或一个内存指针,pos参数将声音数据的起始位置(偏移量)传送给SOUND_DATA类,size参数用于设置流的总字节数(声音的大小)。
注意pos和size的缺省值都是-1,就使得类能够对文件位置进行设置。为了达到设置声音数据源的目的,首先必须使用set_format函数设置回放格式,然后必须使用load_wav_header函数解析波形文件头,此函数所带参数的含义同load_wav函数相同。
另外,如果声音被存储到内存中以及从内存中流出,就必须使用SOUND_DATA::create函数创建此内存缓冲区。可以自己指定缓冲区的大小,也可以让create函数使用通过load_wav_header函数解析出来的缓冲区大小。调用SOUND_DATA::get_ptr函数可以得到指向内存缓冲区的指针,可以安全地使用此指针存储声音。